0

Is there a way to prevent one particular object of being destroyed while another object still exists, without explicitly making the class of the second one to know about the first?

Here is an example:

class A { /* A creates B*/ };
class B {};

int main() {
    auto spA = std::make_shared<A>();
    auto spB = spA->getB();

    spA.reset();
    spB->DoSomething(); 
}

After spA.reset(), I would like to have at least one reference to the object pointed by spA. Or, in other words: I would like to destroy the object pointed by spA only after calling spB->DoSomething().

But, an important prerequisite is B is not allowed to "know" about A (e.g. it is not allowed to hold a pointer/reference to it).

As I understand, shared pointers provide some functionality which can help with dealing with this problem, but I am not sure which one.

EDIT:

The brief structure of A and B look as follows:

class A 
{
public:
    A() 
    { 
        m_B = B::FactoryB(); 
    }
    std::shared_ptr<B> getB()
    {
        return m_B;
    }
private:
    std::shared_ptr<B> m_B; 
};

class B 
{
public:
    static std::shared_ptr FactoryB()
    {
        return std::make_shared<B>();
    }
};

EDIT2 (MY SOLUTION):

So, using suggestion provided by @MSalters I was able to find a solution that works for me. In this case it was much simpler than expected, such that I only needed to adjust A::getB():

    std::shared_ptr<B> getB()
    {
        std::shared<B> spBAlias(this->shared_from_this(), m_B.get());
        return spBAlias;
    }

Here, instead of just returning m_B, A::getB() creates and returns a shared pointer spBAlias of type B (using alias constructor), which influences the reference counting of the A object (provided by this->shared_from_this()). So, calling spA.reset() in the main decreases uses of spA from two to one, making the call of spB->DoSomething() possible. To use shared_from_this(), A needs to inherit from std::enable_shared_from_this<A>:

class A : public std::enable_shared_from_this<A> { /* ... */ }
nickname
  • 35
  • 6
  • I find your question slightly confusing. Why would you call `spA.reset()` if you still want the object to be alive? – AndyG May 04 '21 at 12:26
  • It looks like you're trying to shoehorn manual lifetime management into a `shared_ptr`. This is pretty much guaranteed to not end well. – molbdnilo May 04 '21 at 12:27
  • @AndyG, That's just a simulation of an "unwanted" access for example (e.g. another function/client tries to delete a component which is still used by the other). – nickname May 04 '21 at 12:31
  • There's no way to do this and comply with these strict requirements. C++ does not work this way. It's possible to make this happen, but only by slight changes to these requirements. – Sam Varshavchik May 04 '21 at 12:33
  • to me it looks like you want an explicit relation between the two without having an explicit relation between the two, but maybe I am just misunderstanding what you want to achieve – 463035818_is_not_an_ai May 04 '21 at 12:34
  • Ok, keeping my "requirements" aside, how would you approach this problem? The order of calls to `reset()` and `DoSomething` remains. – nickname May 04 '21 at 12:39
  • why does `B::DoSomething` need the `A` instance to be alive when it has no reference or pointer to it? – 463035818_is_not_an_ai May 04 '21 at 12:40
  • Maybe if you describe your concrete requirements we can help better. Right now it's hard to ignore the part of the brain that goes, "Why would you do this?" I also come up with my composition, normally, by imagining the objects as little people doing little things in a little office. – JohnFilleau May 04 '21 at 12:42
  • also it would help if you turned your code into a [mcve]. My brain goes on strike when you call `B::Dosomething` but `B` has no such method. – 463035818_is_not_an_ai May 04 '21 at 12:45
  • If `B` can't know about `A`, then the it feels like you need some life support object that forces `A` to stay alive until `B` has done what it needs to do. – JohnFilleau May 04 '21 at 12:48
  • `B` has member variables which hold references to some of the member variables from `A`. The initialization of those members happens during creating of `B` inside `A`'s constructor. This is why I need `A` object to be alive when I call `B::DoSomething()`. So if for example `B` is in use by somebody (e.g. performing `DoSomething()`), and someone else is trying to "switch off / take away / deactivate" the `A` object, this should be prevented, since its resources are still occupied by `B`. – nickname May 04 '21 at 12:51

2 Answers2

3

You can absolutely do this using shared_ptr, but you'll have to write some extra code.

Just have getB return a shared_ptr<B> with a custom deleter ... the deleter should be a lambda capturing a shared_ptr<A> by value, and explicitly releasing that only when spB goes out of scope and tries to delete the B object.

A will have to derive from std::enable_shared_from_this to get a viable shared pointer to itself from inside A::getB().

If the B object is actually a subobject of A, then this is sufficient. Otherwise, you should really delete the B as well.


int main() {
    shared_ptr<A> spA = std::make_shared<A>();
    shared_ptr<B> spB = spA->getB();

    spA.reset();         // spB's deleter keeps the refcount nonzero
    spB->DoSomething();  // fine
}                        // spB's destructor finally deletes *spA
Useless
  • 64,155
  • 6
  • 88
  • 132
  • thank you! I like really your idea but I am not quite sure how to implement it. Especially the part with the lambda's capture providing `this->shared_from_this()` object confuses me, since I construct the `B` object within a factory function and not in `A::getB()`. I have editted the main question with the structure of `A` and `B`. Could you please elaborate in more detail how to define this custom deleter function in my case? – nickname May 04 '21 at 13:43
  • Ah, I see you've now described B's ownership model a bit, so this should be possible. Anyway, the gist is that `A` must use `shared_from_this` so that `A::getB()` can get a shared pointer to `this`. Then it can return a `shared_ptr` with a deleter that captures that `shared_ptr`. – Useless May 04 '21 at 16:59
2

shared_ptr has something called an aliasing constructor. A shared_ptr<B> can be created from a shared_ptr<A> and a B*. The destructor of this shared_ptr<B> will delete the A object, not the B.

In your case, your A object could itself hold a unique_ptr<B>, or even an outright B member.

MSalters
  • 173,980
  • 10
  • 155
  • 350