Access to owner in C ++ destructor

Say that there is an object A to which object B belongs through std::unique_ptr<B> . Next, B contains a raw pointer to a (weak) reference to A. Then destructor A will call destructor B because it belongs to it.

What would be the safe way to access A in destructor B? (since we can also be in destructor A).

In a safe way, I can explicitly reset the strong reference to B in the destructor of A so that B is destroyed in a predictable way, but what is the general best practice?

+8
c ++ smart-pointers ownership-semantics
source share
5 answers

I am not a lawyer, but I think that everything is in order. You are stepping on dangerous ground and may need to rethink your design, but if you are careful, I think you can simply rely on the members being destroyed in the reverse order, they were announced .

So it's 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 terms A are destroyed in the order n2 , then b , then n1 . But this is not normal

 #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 already destroyed by the moment b tries to use it.

+3
source share

What would be the safe way to access A in destructor B? (since we can also be in destructor A).

There is a safe way :

3.8 / 1

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

- if T is a class type with a nontrivial destructor (12.4), the call to the destructor is launched [...]

I think it’s straightforward that you cannot access the object after its lifespan has expired.

EDIT: As Chris Drew said in a comment, you can use the object after it runs the destructor, sorry, my mistake I missed one important suggestion in the standard:

3.8 / 5

Before the life of the object started, but after the storage in which the object will be occupied, it is allocated or, after the life of the object has expired , and before the storage that occupied the object, reused or released, any pointer that refers to the storage location in which the object will be located or was located can be used, but only in a limited way. For an object under development or destruction, see 12.7 . Otherwise, such a pointer refers to a dedicated storage (3.7.4.2) and uses a pointer as if the pointer was of type void *, well defined. Such a pointer can be dereferenced, but the resulting value of l can only be used in limited ways, as described below. A program has undefined behavior if: [...]

12.7 has a list of things you can do during construction and demolition, some of the most important:

12.7 / 3:

To explicitly or implicitly convert a pointer (glvalue), referring to an object of class X to a pointer (link) to direct or indirect base class B from X , the construction of X and the construction of all its direct or indirect foundations that directly or indirectly follow from B should begin, and the destruction of these classes will not be completed , otherwise conversion will result in unspecified behavior. In order to form a pointer to (or access to the value) of the direct non-static element of the obj object , the construction of obj must begin and its destruction will not be completed , otherwise, the calculation of the value of the pointer (or access to the value of a member) leads to 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 construction or destruction of non-static data elements of classes and the object to which the call is applied, it is an object (name it x) that is under construction or destruction, the function being called is the final lane in the class of constructors or destructors, and not in overriding it in a more derived class. If a virtual function call uses an explicit class member (5.2.5), and the object expression refers to the full object x or one of these subobjects of the base class, but not x or one of its subobjects of the base class, the behavior is undefined.

+3
source share

As already mentioned, there is no “safe path”. In fact, as PcAF pointed out, the lifetime of A has already expired by the time you reach destructor B
I also want to note that this is actually good! There must be a strict order in which objects are destroyed.
Now you should tell B in advance that A is about to collapse.
It is as simple as

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

Passing the this pointer may or may not be necessary depending on the design of ob B (If B contains many links, you may need to find out which one should be separated. If it contains only one, this pointer is superfluous).
Just make sure that the corresponding member functions are private, so that the interface can only be used for its intended purpose.

Note : This is a simple lightweight solution that is great if you have complete control over the relationship between A and B Do not, under any circumstances, design this as a network protocol! This will require a lot more guards.

0
source share

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 destructor A will call destructor B since it belongs to him." This is not the right way to call destructors in the case of compound objects. If you see the example above, a destroyed first, and then b destroyed. a destructor will not call handle b so that the control returns back to a destructor.

"What will be the safe way to access A in destructor B?" . According to the above example, a already destroyed, so a not available in the b destructor.

", since we can also be in the destructor A). This is not true. Again, when the control leaves the destructor a , then only the control enters the destructor b .

A destructor is a member function of class T. After the control exits the destructor, class T cannot be accessed. All class T data members can be accessed in class constructors and destructors in accordance with the above example.

0
source share

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

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

Problems arise if objects A and B extend to other program units. Then you should use tools from std :: memory, such as std :: shared_ptr or std: weak_ptr.

-2
source share

All Articles