Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 272
ksb Avatar asked Jun 22 '16 06:06

ksb


People also ask

Can I call a destructor in member function?

A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ).

Can we use this pointer in destructor?

In one word: YES.

Which operator is used with destructor?

Destructors are invoked when you use the delete operator for objects created with the new operator.

Can a destructor be private?

Destructors with the access modifier as private are known as Private Destructors. Whenever we want to prevent the destruction of an object, we can make the destructor private.


2 Answers

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.

like image 110
Chris Drew Avatar answered Sep 21 '22 05:09

Chris Drew


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.

like image 33
PcAF Avatar answered Sep 24 '22 05:09

PcAF