Advertisement

Can someone please explain + example of 'ownership'

Started by November 09, 2015 01:59 PM
10 comments, last by phil_t 9 years, 1 month ago


That illustrates lifetimes for simple C++ code, and also illustrates a bug that happens when you don't pay attention to lifetimes.

You'll want to swap

ptr = &A;

with

ptr = &B;

to show the bug.

Hello to all my stalkers.

There are several common object lifetime ownership patterns, and the c++ smart pointers are designed to fit naturally into some of those common patterns. So just seeing a particular smart pointer in code can tell you some of the programmer's intent (assuming they knew what they were doing).

For instance, if someone owns an object exclusively - that is, it alone controls the objects lifetime and is responsible for destroying it - this is generally when std::unique_ptr is used. It's a compile error to try and copy a std::unique_ptr, which makes sense, because then two different parts of code would be responsible for destroying the "unique" object. If you had an object A that owned another object B:


class A
{
public:
    A() { b = new B; }
    ~A() { delete b; }

private:
    B *b;
};

If you accidentally copied an instance of A, that would compile just fine. But you'd end up with a "double delete" bug, since only one B was ever allocated, and now it will be deleted twice when the two instances of A go out of scope and are destroyed.

If, however, you used std::unique_ptr to express the ownership:


class A
{
public:
    A() { b = std::make_shared<B>(); }

private:
    std::unique_ptr<B> b;
};

Now an attempt to copy A will result in a compile error (since its member b cannot be copied). And this is good, since copying it would violate the lifetime ownership policy you desired.

Now, suppose you want one B instance to be shared among many owners. More specifically, you want it so that as long as someone is still referring to and using B, B should be kept alive. This is when you'd want to use std::shared_ptr<B> to pass around to the various owners. B will be kept alive as long as someone still has a std::share_ptr<B> to it. Consider the above code again:


class A
{
public:
    A(std::shared_ptr<B> b) { this->b = b; }

private:
    std::shared_ptr<B> b;
};

So you can copy instances of A without any problem, since std::shared_ptr is also copyable. Which makes sense, as the desired was to have many parts of code share ownership of the B. B won't be deleted until all the A's have been destroyed.

Now of course, with std::shared_ptrs, you could end up in a situation with two objects having shared ownership of each other. This is known as a circular reference, and can cause memory leaks, since each object will keep the other alive. std::weak_ptr is one way to address this (another way is to have a guaranteed destruction path that removes the references). std::weak_ptr basically says "I'd like a reference to this object, but I don't want my reference to keep the object alive". Then of course, before using it you need to make sure the object is still alive.

std::shared_ptr can sometimes seem like an easy way out, a way to not think about lifetime management ("the object will be kept alive as long as someone is using it"). Be careful you don't use it like that. There are specific use cases for shared_ptr, but using it indiscriminately will only cause headaches. In general, I find myself using shared_ptr very rarely.

What about raw pointers? When you have a raw pointer, the intent can be stated as "this will be kept alive beyond your lifetime". Of course, there's nothing in the compiler enforcing this, but it's a useful rule. For instance, passing an object pointer to a function that calls some methods on it and returns: the object that the pointer points to is presumed to still exist through the duration of the function. The function doesn't really need to know anything about the lifetime management for that object.

At any rate, while you certainly don't need to use smart pointers, they will help express the intent of your code (in addition to automatically cleaning up after themselves, etc...).

Managed languages like C# or java let you get away with not thinking about lifetime management issues so much (which can be a good thing or a bad thing, depending how you look at it). C++ is unforgiving though. But with enough practice it will become natural when you're designing your architecture (and writing your code) to think about who should own what.

This topic is closed to new replies.

Advertisement