【发布时间】:2020-05-28 11:09:57
【问题描述】:
如果类A包含类B,那么当A析构时,会先调用B的析构函数,即它们嵌套关系的倒序。
但是如果A 包含B 的shared_ptr,而B 包含指向A 的原始指针,我们应该如何处理析构函数以使其安全?
考虑以下示例:
#include <iostream>
#include <memory>
#include <unistd.h>
struct B;
struct A {
int i = 1;
std::shared_ptr<B> b;
A() : b(std::make_shared<B>(this)) {}
~A() {
b = nullptr;
std::cout << "A destruct done" << std::endl;
}
};
struct B {
A *a;
B(A *aa) : a(aa) {}
~B() {
usleep(2000000);
std::cout << "value in A: " << a->i << std::endl;
std::cout << "B destruct done" << std::endl;
}
};
int main() {
std::cout << "Hello, World!" << std::endl;
{
A a;
}
std::cout << "done\n";
return 0;
}
你可以在A的析构函数中看到,我明确地将b设置为nullptr,这将立即触发B的析构函数,并阻塞直到它完成。
输出将是:
Hello, World!
value in A: 1
B destruct done
A destruct done
done
但是如果我注释掉那一行
~A() {
// b = nullptr; // <---
std::cout << "A destruct done" << std::endl;
}
输出将是:
Hello, World!
A destruct done
value in A: 1
B destruct done
done
似乎A 的析构函数在没有等待B 销毁的情况下完成。但是在这种情况下,我预计会出现段错误,因为当A 已经被破坏时,B 试图访问A 的成员,这是无效的。但是为什么程序不会产生段错误呢?碰巧没问题(即undefined behavior)吗?
还有,当我改变时
{
A a;
}
到
A * a = new A();
delete a;
输出还是一样,没有段错误。
【问题讨论】:
-
破坏以相反的顺序发生。您应该能够遵循这一点并弄清楚会发生什么。这是一个直接的串行过程。顺便说一句;将变量设置为
nullptr(或类似)会释放 no 内存。如果你的程序包含UB,那么all都是无效的,anything都是OK的。 -
当你使用不完整的类型
B时,我什至不明白代码如何工作,甚至编译 -
"A destruct done"有点误导:它在销毁过程中被打印在 dtor 内部。不是在它之后。我看到你之前已经重置了智能指针,所以没有什么重要的事情要做了。但一般来说,在破坏成员和基地之前在体内打印它是一个坏主意(这里没有基地,但我提出一个一般性观点)。 -
显然,只要您稍后提供完整的定义,GCC 就可以容忍前向声明。这对我来说似乎很可疑。
标签: c++ c++11 shared-ptr