ksb
ksb

Reputation: 710

Accessing owner in destructor c++

Say there is an object A which owns an object B via std::unique_ptr<B>. Further B holds a raw pointer(weak) reference to A. Then the destructor of A will invoke the destructor of B, since it owns it.

What will be a safe way to access A in the destructor of B? (since we may also be in the destructor of A).

A safe way me be to explicitly reset the strong reference to B in the destructor of A, so that B is destroyed in a predictable manner, but what's the general best practice?

Upvotes: 9

Views: 1069

Answers (5)

Roland
Roland

Reputation: 336

If you look only on the relations of the two classes A and B, the construction is well:

class A {
    B son;
    A(): B(this)  {}
};   
class B {
    A* parent;
    B(A* myparent): parent(myparent)  {} 
    ~B() {
        // do not use parent->... because parent's lifetime may be over
        parent = NULL;    // always safe
    }
}

The problems arise, if objects of A and B are proliferated to other program units. Then you should use the tools from std::memory like std::shared_ptr or std:weak_ptr.

Upvotes: -2

PcAF
PcAF

Reputation: 2007

What will be a safe way to access A in the destructor of B? (since we may also be in the destructor of A).

There isn't safe way:

3.8/1

[...]The lifetime of an object of type T ends when:

— if T is a class type with a non-trivial destructor (12.4), the destructor call starts [...]

I think it's straightforward that you can't access object after it's lifetime has ended.

EDIT: As Chris Drew wrote in comment you can use object after it's destructor started, sorry, my mistake I missed out one important sentence in the standard:

3.8/5

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a pointer refers to allocated storage (3.7.4.2), and using the pointer as if the pointer were of type void*, is well-defined. Such a pointer may be dereferenced but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if: [...]

In 12.7 there is list of things you can do during construction and destruction, some of the most important:

12.7/3:

To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior. To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.

12.7/4

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class. If the virtual function call uses an explicit class member access (5.2.5) and the object expression refers to the complete object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, the behavior is undefined.

Upvotes: 3

Chris Drew
Chris Drew

Reputation: 15334

I'm no language lawyer but I think it is OK. You are treading on dangerous ground and perhaps should rethink your design but if you are careful I think you can just rely on the fact that members are destructed in the reverse order they were declared.

So this is OK

#include <iostream>

struct Noisy {
    int i;
    ~Noisy() { std::cout << "Noisy " << i << " dies!" << "\n"; }
};

struct A;

struct B {
    A* parent;
    ~B();
    B(A& a) : parent(&a) {}
};

struct A {
    Noisy n1 = {1};
    B     b;
    Noisy n2 = {2};
    A() : b(*this) {}
};

B::~B() { std::cout << "B dies. parent->n1.i=" << parent->n1.i << "\n"; }

int main() {
    A a;
}

Live demo.

since the members of A are destructed in order n2 then b then n1. But this is not OK

#include <iostream>

struct Noisy {
    int i;
    ~Noisy() { std::cout << "Noisy " << i << " dies!" << "\n"; }
};

struct A;

struct B {
    A* parent;
    ~B();
    B(A& a) : parent(&a) {}
};

struct A {
    Noisy n1 = {1};
    B     b;
    Noisy n2 = {2};
    A() : b(*this) {}
};

B::~B() { std::cout << "B dies. parent->n2.i=" << parent->n2.i << "\n"; }

int main() {
    A a;
}

Live demo.

since n2 has already been destroyed by the time B tries to use it.

Upvotes: 4

sameerkn
sameerkn

Reputation: 2259

Consider this:

struct b
{
        b()
        {
                cout << "b()" << endl;
        }
        ~b()
        {
                cout << "~b()" << endl;
        }
};

struct a
{
        b ob;
        a()
        {
                cout << "a()" << endl;
        }
        ~a()
        {
                cout << "~a()" << endl;
        }
};

int main()
{
        a  oa;
}

//Output:
b()
a()
~a()
~b()

"Then the destructor of A will invoke the destructor of B, since it owns it." This is not the correct way of invocation of destructors in case of composite objects. If you see above example then, first a gets destroyed and then b gets destroyed. a's destructor won't invoke b's destructor so that the control would return back to a's destructor.

"What will be a safe way to access A in the destructor of B?". As per above example a is already destroyed therefore a cannot be accessed in b's destructor.

"since we may also be in the destructor of A).". This is not correct. Again, when the control goes out of a's destructor then only control enters b's destructor.

Destructor is a member-function of a class T. Once the control goes out of destructor, the class T cannot be accessed. All the data-members of class T can be accessed in class T's constructors and destructor as per above example.

Upvotes: 0

iolo
iolo

Reputation: 1090

As has already been mentioned there is no "safe way". In fact as has been pointed out by PcAF the lifetime of A has already ended by the time you reach B's destructor.
I also just want to point out that this is actually a good thing! There has to be a strict order in which objects get destroyed.
Now what you should do is tell B beforehand that A is about to get destructed.
It is as simple as

void ~A( void ) {
    b->detach_from_me_i_am_about_to_get_destructed( this );
}

Passing the this pointer might be necessary or not depending on the design ob B (If B holds many references, it might need to know which one to detach. If it only holds one, the this pointer is superfluous).
Just make sure that appropriate member functions are private, so that the interface only can be used in the intended way.

Remark: This is a simple light-weight solution that is fine if you yourself completely control the communication between A and B. Do not under any circumstances design this to be a network protocol! That will require a lot more safety fences.

Upvotes: 0

Related Questions