6

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?

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 1
    That sounds like a nightmare. How would you reason about such "optional ownership"? Is the pointee uniquely yours, or are you sharing it with others? Thread safe? Re-entrant? Or all yours? – Kerrek SB Jun 05 '14 at 00:13
  • 1
    `std::shared_ptr` allow even these shenanigans. But you must manually set the proper deleter if that's different from the default deleter, like a null-deleter. Alternatively, use a `shared_ptr` pointing to the owning object for completely correct semantics. – Deduplicator Jun 05 '14 at 00:18
  • @Deduplicator the deleter is a template argument, so you'd have to write a deleter that handles both cases. You can't just pass a null deleter in one case and a regular deleter in another. – Adam Jun 05 '14 at 00:23
  • To answer the question, "is the best solution just to pass an ownership flag to B?". Yes. – Adam Jun 05 '14 at 00:25
  • @Adam: The decision how to construct the `shared_ptr` is the same as passing that ownership flag resp. even better following my second way to construct it. – Deduplicator Jun 05 '14 at 00:26
  • @KerrekSB: If constructed as `B(new C())` the pointee is owned by B. If constructed any other way, the pointee would not be owned by B. – nneonneo Jun 05 '14 at 00:39
  • @KerrekSB: It can be done reasonably, using `std::shared_ptr`s for example. That does not mean that such is justified in the OPs case, but it could be. – Deduplicator Jun 05 '14 at 01:00
  • How is this a duplicate of "stack vs. heap"? I'm not asking to distinguish two pointers from each other, because *I already know which one they are*. I just want to know how to tell that to B in a sane way. – nneonneo Jun 05 '14 at 01:01
  • OK, so it's clear that I'm being told that the design is bad. I'm actually very new to shared and smart pointers, having only used `T *` in the past, but I'd appreciate knowing if there's a better solution here... – nneonneo Jun 05 '14 at 01:03
  • Just open it, and I'll try to write a reasonable answer... Anyway, discussed another one with Jeffrey here just a moment ago: http://stackoverflow.com/questions/24049401/use-smart-or-raw-pointer See the discussion, the reopen time and his post time ;-) – Deduplicator Jun 05 '14 at 01:10
  • It may help to use `std::weak_ptr` - this is used for pointing to the same thing as a `shared_ptr` but without the ownership semantics. – M.M Jun 05 '14 at 02:16
  • @Deduplicator: You might find [this video](http://www.youtube.com/watch?v=vxv74Mjt9_0) interesting. The speaker reckons that a "shared pointer is essentially a global variable". – Kerrek SB Jun 05 '14 at 08:15
  • @Adam Sure you can pass different deleters to shared_ptr. The deleter is removed with type erasure, so two shared_ptr with different deleters are compatible. – nwp Jun 19 '14 at 11:37

4 Answers4

2

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.
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
1

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()) {}
};
R Sahu
  • 204,454
  • 14
  • 159
  • 270
1

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
c z
  • 7,726
  • 3
  • 46
  • 59
0

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?

Community
  • 1
  • 1
Deepscorn
  • 822
  • 10
  • 22
  • I don't feel this warrants the overhead (and confusion) of a *shared* pointer, it's not *shared*. The object is either owned or unowned, and we know which one at compile time, we just need a nice API that handles both cases. – c z Jun 09 '23 at 11:38