C++ 销毁顺序:在 class 析构函数之前调用字段析构函数

C++ destruction order: Calling a field destructor before the class destructor

有没有办法在 class 析构函数之前调用字段析构函数?

假设我有 2 classes SmallBig,并且 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 析构函数执行一些必要的清理工作。

我可以:

  1. 显式调用 small.~Small() 析构函数。 -> 然而,这会调用 Small 析构函数两次:一次显式调用,一次在执行 Big 析构函数之后。
  2. 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::optionalstd::unique_ptrstd::shared_ptr 可能足以满足此目的。