nneonneo
nneonneo

Reputation: 179422

Smart pointers with optional ownership

I'm trying to allow a class to contain a pointer, which may either be an owned pointer or a borrowed pointer. In the former case, it should destroy the owned object itself; in the latter case, it shouldn't destroy the pointed-to object.

In code, I have classes A, B and C. I'm aiming for the following (simplified) definitions, where B is the class that needs to own a pointer:

class C {
    ...
};

class B {
    C *c;
    B(C *c) : c(c) {
    }
};

class A {
    C c1;
    B b1, b2;
    // b2 leaks pointer to C
    A() : b1(&c1), b2(new C()) {
    }
};

When an instance of A destructs, it destroys c1, b1 and b2. Ideally, the destruction of b2 should delete the anonymous C instance, but the destruction of b1 should not delete anything (since c1 will be destroyed by A directly).

What kind of smart pointer can I use to achieve this? Or, is the best solution just to pass an ownership flag to B?

Upvotes: 6

Views: 1873

Answers (4)

c z
c z

Reputation: 8977

Pass in a unique_ptr via std::move for the owned version and pass in a reference for the unowned version:

  • no runtime overhead outside a unique_ptr
  • avoids incorrect usage
  • avoids having to mess around with custom destructors
  • no ambiguity

Minimal working example:

#include <iostream>
#include <memory>

class C
{
public:
    ~C() { std::cout << "Goodbye\n"; }

    void SayHello() { std::cout << "Hello\n"; }
};

class B
{
    std::unique_ptr<C> owned;
    C* unowned;

public:
    B(C& c) : owned(nullptr)
            , unowned(&c)
    { }

    B(std::unique_ptr<C> c) : owned(std::move(c))
                            , unowned(owned.get())
    { }

    C& GetC() { return *unowned; }
};

int main()
{
    C stackC;
    std::unique_ptr<C> heapC(new C);

    B b1(stackC);
    B b2(std::move(heapC));

    b1.GetC().SayHello();
    b2.GetC().SayHello();

}

OUTPUT:

Hello
Hello
Goodbye
Goodbye

Upvotes: 1

Deepscorn
Deepscorn

Reputation: 832

There is no way to archive this behavior without side effects, as far as I know. If it is just usual pointers (not COM), than you can access C via shared_ptr in both classes. If only B owns C, than they both will be destroyed with B's destroy. If both A & B owns C, than C will be destoyed only when last remaining alive owner (be it A or B) will be destroyed.

I know such practice to think about ownership: If method gets just a normal pointer, than it is meant that pointer will be used only inside that method. So, B will be:

class B1 {
    B(C *c) {
      //do some staff with c
    }
    void doSomeStaff(C*) {}
};

Or using & (cleaner, if your framework accept it):

class B2 {
    B(C& c) {
      //do some staff with c
    }
    void doSomeStaff(C&) {}
};

If method gets a shared pointer, it need this pointer for future reuse (keep it):

class B3 {
public:
    std::shared_ptr<C> c;
    B(std::shared_ptr<C> c) : c(c) {
    }
};

So, now you can call b1.doSomeStaff(b3.c) or b2.doSomeStaff(*b3.c) without thinking who must destroy the pointed object C. You know only, that this object will be used in b1. That's all.

Do not forget to specify that you need shared_ptr, not C* in method - shared_ptr is an object, which increments reference count to object when copied. And not increments, but creates a new shared_ptr with reference count = 1, when constructed from C*.

This is not the answer to your question, but some of the common uses. See unique_ptr in Deduplicator's in answer. Also check: http://www.boost.org/doc/libs/1_55_0/libs/smart_ptr/smart_ptr.htm. Even if you don't use boost, there is a good theory for using different approaches to hold objects. Also check this answer: What is a smart pointer and when should I use one?

Upvotes: 0

Deduplicator
Deduplicator

Reputation: 45654

If you are sure and can guarantee that the reused C will not be destroyed early (triple check that), there are multiple ways to go about it.
Some you might consider:

  1. You can manually manage the pointer and a flag. Make sure you get the copy-semantic right, e.g. like this:

    class B {
        std::unique_ptr<C> c;
        bool shared = false;
    
        B(C& c) : c(&c), shared(true) {}
        B(C *c = 0) : c(c) {}
        ~B() { if (shared) c.release(); }
    };
    
  2. You could use a custom deleter, like this:

    template <class T> struct maybe_delete
    {
        void operator()(T* p) const noexcept {if(!shared) delete p;}
        bool shared = false;
    };
    template <class T> struct maybe_delete<T[]>
    {
        void operator()(T* p) const noexcept {if(!shared) delete [] p;}
        template <class U> void operator()(U*) const = delete;
        bool shared = false;
    };
    
    class B {
        std::unique_ptr<C, maybe_delete> c;
    
        B(C& c) : B(&c) {this->c.get_deleter().shared = true;}
        B(C *c) : c(c) {}
    };
    
  3. You could take a peek at std::shared_ptr, though that is probably severe overkill and might have too much overhead for you.

Upvotes: 2

R Sahu
R Sahu

Reputation: 206577

While I fear for the potential abuse that B is open to, you could do this:

class B {
    C *c;
    bool owned;

    B(C& c) : c(&c), owned(false) {}
    B(C *c) : c(c), owned(true) {}
    ~B() { if (owned) delete c; }
};

class A {
    C c1;
    B b1, b2;
    A() : b1(c1), b2(new C()) {}
};

Upvotes: 1

Related Questions