C++ 销毁顺序:在 class 析构函数之前调用字段析构函数
C++ destruction order: Calling a field destructor before the class destructor
有没有办法在 class 析构函数之前调用字段析构函数?
假设我有 2 classes Small
和 Big
,并且 Big
包含一个 Small
的实例作为其字段:
class Small
{
public:
~Small() {std::cout << "Small destructor" << std::endl;}
};
class Big
{
public:
~Big() {std::cout << "Big destructor" << std::endl;}
private:
Small small;
};
int main()
{
Big big;
}
这当然是在小析构函数之前调用大析构函数:
Big destructor
Small destructor
我需要在 Big
析构函数之前调用 Small
析构函数,因为它会为 Big
析构函数执行一些必要的清理工作。
我可以:
- 显式调用
small.~Small()
析构函数。 -> 然而,这会调用 Small
析构函数两次:一次显式调用,一次在执行 Big
析构函数之后。
- 将
Small*
作为字段并在 Big
析构函数中调用 delete small;
我知道我可以在 Small
class 中有一个函数来执行清理并在 Big
析构函数中调用它,但我想知道是否有反转析构函数顺序的方法。
有更好的方法吗?
不知道为什么你想这样做,我唯一的建议是将 Big
分解成 Small
之后需要销毁的部分从其余部分开始,然后使用组合将其包含在 Big
中。那么你就可以控制销毁的顺序了:
class Small
{
public:
~Small() {std::cout << "Small destructor" << std::endl;}
};
class BigImpl
{
public:
~BigImpl() { std::cout << "Big destructor" << std::endl; }
};
class Big
{
private:
BigImpl bigimpl;
Small small;
};
call the small.~Small() destructor explicitly. -> This, however, calls the small destructor twice: once explicitly, and once after the big destructor has been executed.
好吧,我不知道你为什么要继续这个有缺陷的设计,但你可以使用新的放置解决第一个项目符号中描述的问题。
它遵循一个最小的工作示例:
#include <iostream>
struct Small {
~Small() {std::cout << "Small destructor" << std::endl;}
};
struct Big {
Big() { ::new (storage) Small; }
~Big() {
reinterpret_cast<Small *>(storage)->~Small();
std::cout << "Big destructor" << std::endl;
}
Small & small() {
return *reinterpret_cast<Small *>(storage);
}
private:
unsigned char storage[sizeof(Small)];
};
int main() {
Big big;
}
您不再有 Small
类型的变量,但是使用示例中的 small
成员函数,您可以轻松解决它。
这个想法是你保留足够的 space 来构建就地 Small
然后你可以像你一样显式地调用它的析构函数。它不会被调用两次,因为 Big
class 必须释放的是一个 unsigned char
数组。
此外,您不会将 Small
直接存储到动态存储中,因为实际上您正在使用 Big
的数据成员来创建它。
也就是说,我建议您将其分配到动态存储上,除非您有充分的理由不这样做。使用 std::unique_ptr
并在 Big
的析构函数的开头重置它。您的 Small
将在析构函数的主体按预期实际执行之前消失,而且在这种情况下,析构函数不会被调用两次。
编辑
正如评论中所建议的,std::optional
可以是替代 std::unique_ptr
的另一种可行解决方案。请记住,std::optional
是 C++17 的一部分,因此您能否使用它主要取决于您必须遵守的标准修订版。
无法更改析构函数调用的顺序。正确的设计方法是 Small
执行自己的清理。
如果您无法更改 Small
,那么您可以制作一个包含 Small
的 class SmallWrapper
,并且还可以执行所需的清理。
标准容器 std::optional
或 std::unique_ptr
或 std::shared_ptr
可能足以满足此目的。
有没有办法在 class 析构函数之前调用字段析构函数?
假设我有 2 classes Small
和 Big
,并且 Big
包含一个 Small
的实例作为其字段:
class Small
{
public:
~Small() {std::cout << "Small destructor" << std::endl;}
};
class Big
{
public:
~Big() {std::cout << "Big destructor" << std::endl;}
private:
Small small;
};
int main()
{
Big big;
}
这当然是在小析构函数之前调用大析构函数:
Big destructor
Small destructor
我需要在 Big
析构函数之前调用 Small
析构函数,因为它会为 Big
析构函数执行一些必要的清理工作。
我可以:
- 显式调用
small.~Small()
析构函数。 -> 然而,这会调用Small
析构函数两次:一次显式调用,一次在执行Big
析构函数之后。 - 将
Small*
作为字段并在Big
析构函数中调用delete small;
我知道我可以在 Small
class 中有一个函数来执行清理并在 Big
析构函数中调用它,但我想知道是否有反转析构函数顺序的方法。
有更好的方法吗?
不知道为什么你想这样做,我唯一的建议是将 Big
分解成 Small
之后需要销毁的部分从其余部分开始,然后使用组合将其包含在 Big
中。那么你就可以控制销毁的顺序了:
class Small
{
public:
~Small() {std::cout << "Small destructor" << std::endl;}
};
class BigImpl
{
public:
~BigImpl() { std::cout << "Big destructor" << std::endl; }
};
class Big
{
private:
BigImpl bigimpl;
Small small;
};
call the small.~Small() destructor explicitly. -> This, however, calls the small destructor twice: once explicitly, and once after the big destructor has been executed.
好吧,我不知道你为什么要继续这个有缺陷的设计,但你可以使用新的放置解决第一个项目符号中描述的问题。
它遵循一个最小的工作示例:
#include <iostream>
struct Small {
~Small() {std::cout << "Small destructor" << std::endl;}
};
struct Big {
Big() { ::new (storage) Small; }
~Big() {
reinterpret_cast<Small *>(storage)->~Small();
std::cout << "Big destructor" << std::endl;
}
Small & small() {
return *reinterpret_cast<Small *>(storage);
}
private:
unsigned char storage[sizeof(Small)];
};
int main() {
Big big;
}
您不再有 Small
类型的变量,但是使用示例中的 small
成员函数,您可以轻松解决它。
这个想法是你保留足够的 space 来构建就地 Small
然后你可以像你一样显式地调用它的析构函数。它不会被调用两次,因为 Big
class 必须释放的是一个 unsigned char
数组。
此外,您不会将 Small
直接存储到动态存储中,因为实际上您正在使用 Big
的数据成员来创建它。
也就是说,我建议您将其分配到动态存储上,除非您有充分的理由不这样做。使用 std::unique_ptr
并在 Big
的析构函数的开头重置它。您的 Small
将在析构函数的主体按预期实际执行之前消失,而且在这种情况下,析构函数不会被调用两次。
编辑
正如评论中所建议的,std::optional
可以是替代 std::unique_ptr
的另一种可行解决方案。请记住,std::optional
是 C++17 的一部分,因此您能否使用它主要取决于您必须遵守的标准修订版。
无法更改析构函数调用的顺序。正确的设计方法是 Small
执行自己的清理。
如果您无法更改 Small
,那么您可以制作一个包含 Small
的 class SmallWrapper
,并且还可以执行所需的清理。
标准容器 std::optional
或 std::unique_ptr
或 std::shared_ptr
可能足以满足此目的。