在析构函数 C++ 中访问所有者
Accessing owner in destructor c++
假设有一个对象 A 通过 std::unique_ptr<B>
拥有一个对象 B。此外,B 持有对 A 的原始指针(弱)引用。然后 A 的析构函数将调用 B 的析构函数,因为它拥有它。
在 B 的析构函数中访问 A 的安全方法是什么? (因为我们也可能在 A 的析构函数中)。
一种安全的方法是在 A 的析构函数中显式重置对 B 的强引用,以便以可预测的方式销毁 B,但一般的最佳做法是什么?
What will be a safe way to access A in the destructor of B? (since we may also be in the destructor of A).
没有安全的方法:
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 [...]
我认为在对象的生命周期结束后您无法访问对象是很简单的。
编辑:正如 Chris Drew 在评论中所写,您 可以 在析构函数启动后使用对象,抱歉,我的错误是我错过了标准中的一个重要句子:
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:
[...]
在 12.7 中,列出了您可以在构建和销毁过程中执行的操作,其中一些最重要:
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.
如果只看类A和B这两个关系,构造得很好:
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
}
}
如果 A 和 B 的对象扩散到其他程序单元,就会出现问题。那么您应该使用 std::memory 中的工具,例如 std::shared_ptr 或 std:weak_ptr。
如前所述,没有 "safe way"。事实上,正如 PcAF 所指出的那样,当您到达 B
的析构函数时,A
的生命周期已经结束。
我也只想指出,这实际上是一件好事!销毁对象必须有严格的顺序。
现在你应该做的是预先告诉 B
A
即将被破坏。
就这么简单
void ~A( void ) {
b->detach_from_me_i_am_about_to_get_destructed( this );
}
是否需要传递 this
指针取决于设计 ob B
(如果 B
持有许多引用,它可能需要知道分离哪个。如果它只有一个,this
指针是多余的)。
只需确保适当的成员函数是私有的,以便接口只能以预期的方式使用。
备注:
这是一个简单的轻量级解决方案,如果您自己完全控制 A
和 B
之间的通信就可以了。 在任何情况下都不要将其设计为网络协议!这将需要更多的安全围栏。
考虑一下:
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." 这不是复合对象调用析构函数的正确方法。如果你看到上面的例子,首先 a
被销毁,然后 b
被销毁。 a
的析构函数不会调用 b
的析构函数,因此控件将 return 返回到 a
的析构函数。
"What will be a safe way to access A in the destructor of B?"。根据上面的示例 a
已经被销毁,因此 a
无法在 b
的析构函数中访问。
"since we may also be in the destructor of A)."。这是不正确的。同样,当控制离开 a
的析构函数时,只有控制进入 b
的析构函数。
析构函数是class T 的成员函数。一旦控制脱离析构函数,就无法访问class T。 class T 的所有数据成员都可以在 class T 的构造函数和析构函数中访问,如上例所示。
我不是语言律师,但我认为没关系。你正踩在危险的地面上,也许应该重新考虑你的设计,但如果你小心,我认为你可以依靠 members are destructed in the reverse order they were declared.
这一事实
这样就可以了
#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;
}
因为 A
的成员按顺序销毁 n2
然后 b
然后 n1
。但这不行
#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;
}
因为 n2
在 B
尝试使用它时已经被摧毁。
假设有一个对象 A 通过 std::unique_ptr<B>
拥有一个对象 B。此外,B 持有对 A 的原始指针(弱)引用。然后 A 的析构函数将调用 B 的析构函数,因为它拥有它。
在 B 的析构函数中访问 A 的安全方法是什么? (因为我们也可能在 A 的析构函数中)。
一种安全的方法是在 A 的析构函数中显式重置对 B 的强引用,以便以可预测的方式销毁 B,但一般的最佳做法是什么?
What will be a safe way to access A in the destructor of B? (since we may also be in the destructor of A).
没有安全的方法:
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 [...]
我认为在对象的生命周期结束后您无法访问对象是很简单的。
编辑:正如 Chris Drew 在评论中所写,您 可以 在析构函数启动后使用对象,抱歉,我的错误是我错过了标准中的一个重要句子:
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: [...]
在 12.7 中,列出了您可以在构建和销毁过程中执行的操作,其中一些最重要:
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.
如果只看类A和B这两个关系,构造得很好:
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
}
}
如果 A 和 B 的对象扩散到其他程序单元,就会出现问题。那么您应该使用 std::memory 中的工具,例如 std::shared_ptr 或 std:weak_ptr。
如前所述,没有 "safe way"。事实上,正如 PcAF 所指出的那样,当您到达 B
的析构函数时,A
的生命周期已经结束。
我也只想指出,这实际上是一件好事!销毁对象必须有严格的顺序。
现在你应该做的是预先告诉 B
A
即将被破坏。
就这么简单
void ~A( void ) {
b->detach_from_me_i_am_about_to_get_destructed( this );
}
是否需要传递 this
指针取决于设计 ob B
(如果 B
持有许多引用,它可能需要知道分离哪个。如果它只有一个,this
指针是多余的)。
只需确保适当的成员函数是私有的,以便接口只能以预期的方式使用。
备注:
这是一个简单的轻量级解决方案,如果您自己完全控制 A
和 B
之间的通信就可以了。 在任何情况下都不要将其设计为网络协议!这将需要更多的安全围栏。
考虑一下:
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." 这不是复合对象调用析构函数的正确方法。如果你看到上面的例子,首先 a
被销毁,然后 b
被销毁。 a
的析构函数不会调用 b
的析构函数,因此控件将 return 返回到 a
的析构函数。
"What will be a safe way to access A in the destructor of B?"。根据上面的示例 a
已经被销毁,因此 a
无法在 b
的析构函数中访问。
"since we may also be in the destructor of A)."。这是不正确的。同样,当控制离开 a
的析构函数时,只有控制进入 b
的析构函数。
析构函数是class T 的成员函数。一旦控制脱离析构函数,就无法访问class T。 class T 的所有数据成员都可以在 class T 的构造函数和析构函数中访问,如上例所示。
我不是语言律师,但我认为没关系。你正踩在危险的地面上,也许应该重新考虑你的设计,但如果你小心,我认为你可以依靠 members are destructed in the reverse order they were declared.
这一事实这样就可以了
#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;
}
因为 A
的成员按顺序销毁 n2
然后 b
然后 n1
。但这不行
#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;
}
因为 n2
在 B
尝试使用它时已经被摧毁。