1

After reading on RAII, viewing Herb Sutter's CppCon2014 presentation, and reading the core guidelines and related articles over the course of some days, I'm still quite confused on ownership and related semantics.

Let's say class A and class B represent physical entities, and there's a Scene class and a Process class. The Process class is a main function, if you will. In the real world, an A can acquire a B and physically keep it for itself. It can also release it. During the course of a Process instance, an A object must therefore be able to have for itself a B instance, and also to release it after it's done with it. As and Bs live in a Scene, which a Process must manage.

Also B is derived: an A uses some interface, while a Scene uses some other interface a B provides.

Let's try with some code for at least the Process:

class Process
{
public:
  void run();
};

void Process::run()
{
  auto scn = Scene {};
  auto a = A {};
  auto b = B {};

  scn.add(a);  // stores a
  scn.add(b);  // stores b

  a.acquire(b);  // stores b, and represents physical possession of a B
  a.doSomething();
  a.release(b);  // sets a's B instance to null, and physically loses the B
}

Correct me here if I'm wrong, this is the dubious part.

From what I understand, A should (instead of like the code) be on the heap and pointed to from a shared_ptr, since the Process and the Scene both have their own instance of A. The same would happen for B, which is stored both in a and in scn, and which is in process. Why would then scn not be make_uniqued?

The other way to do this is have everything on the stack (like in the code snippet). Both solutions seem identical to me, I don't understand the semantic difference of these two options at all, but I would tend to the first one.

aPonza
  • 492
  • 4
  • 10
  • I find this question somewhat unclear because I am not really sure what the various functions do and how each type receives and then stores the external objects passed in. By reference? By copy? And are the classes storing references or what? – Galik Nov 23 '18 at 09:57
  • It's kind of the point: at the design level I don't understand if I'm supposed to represent what I have to do in the heap or the stack. I think the biggest issue is with objects B which inherit interfaces. – aPonza Nov 23 '18 at 11:17
  • It would depend on what you are designing and why. – Galik Nov 23 '18 at 11:18

1 Answers1

3

C++ ownership essentially boils down to "who is responsible for deleting this particular object were they to die at this particular moment". In your example, as all objects have automatic lifetime, all of them are owned by Process::run itself. There's no ownership transfer involved in any of add, acquire or release.

Now, what about the same thing with dynamic-lifetime objects? Your description (where I assume by C you mean Scene) can be implemented in two different ways still:

  • Scene holds std::unique_ptrs to A's, that's a given
  • Scene and A can hold either std::unique_ptrs or std::shared_ptrs to B.

The choice in the second bullet can be made through the definition of ownership I outlined above. What happens when and A dies while holding a B?

  • If the B should die as well, then A is its sole owner and should hold it via std::unique_ptr
  • If the B should remain inside the Scene, then Scene and A are both owners, and should both hold the B through a std::shared_ptr

This doesn't include non-owning (raw) pointers, which can be useful (for example, if A owns a B but Scene still needs a direct reference for whatever purpose). These must be updated separately.

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • This seems on point, just some clarifications (and C was a Process, maybe the reason for my second question): - it's still unclear what sets the preference for heap or stack, and I believe I'd like to use the heap because of B being used polymorphically: would that make or break the choice like I think (provided I have no knowledge of TMP and static polymorphism)? - `Scene holds std::unique_ptr to A is a given` is unclear: are you suggesting A should be `make_unique`d in Process::run and passed to Scene by `const&` ("In and retain a copy", semantically) and `make_unique`d in Scene::add? – aPonza Nov 23 '18 at 11:39
  • 2
    @LaboratorioCobotica you use dynamic allocation ("the heap") when you want an object to have custom lifetime (neither static nor automatic), and/or when you want to pass it around type-erased (e.g. through a pointer to its base class). You can't copy a `unique_ptr` (that's why it's guaranteed to be unique), so I mean that `Scene` would obtain the pointer somehow (potentially by moving the pointer into it) and keep ownership of it. `Process` does not really change anything in your example, as it only holds the `Scene` which does all ofthe work. – Quentin Nov 23 '18 at 12:19
  • But Process does change stuff: if I move `a` in Scene I wouldn't be able to call `a.acquire(b);` afterwards (in Process::run) as that's dereferencing a nullptr, isn't it? Maybe a shared_ptr for A as well? If this is correct I might have the situation clear. **EDIT** just to be clear, it would be `a->acquire(b);` since `a` would have been a unique_ptr. – aPonza Nov 23 '18 at 14:49
  • 2
    @LaboratorioCobotica indeed, if the code kept its current structure. That's where you should treat lifetimes & ownership separately from access. For example, you could move `a`'s pointee into the `Scene` but keep a `A *` so you can refer to it later. – Quentin Nov 23 '18 at 14:55