在指针成员之前调用非指针成员的析构函数

3

Consider three classes like this:

class A {
    int bar;
};

class B {
    A* a;
public:
    B(*A na)
    : a(na)
    {}

    ~B() {
        a->bar = 0;
    }
};

class Foo {
    A* a;
    B b;
public:
    Foo()
    : a(new A)
    , b(a)
    {}

    ~Foo() {
        delete a;
    }
};

创建Foo的一个实例,会产生一个指向A和拥有相同指针的BFoo

删除Foo实例时,首先会删除指针a,然后调用b的析构函数,但是~B试图访问在~Foo中被释放的a,导致了分段错误。
有没有办法在运行~Foo体之前调用b的析构函数?
我知道可以通过使b成为指针或使a成为非指针来解决这个问题。如果A是抽象的,后者将无法工作,我想知道是否可以在不使b成为指针的情况下解决这个问题。
注:AB都来自库,因此我无法更改它们的实现。

1
当你说“将a变成指针”时,你忘记输入的缺失单词应该是“聪明”,而不是“不”。正确的答案是a应该是一个智能指针,这样就可以按正确的顺序删除它。 - JSF
1
这并不是真正回答你的问题,但我建议只需将B作为指针。你可以使用智能指针来实现你所要求的功能,但我认为依赖于成员顺序的销毁顺序是一种危险的做法,除非你有一个非常好的理由,而我不认为这是一个好的理由。最好将它们都作为指针,并按正确的顺序进行析构,同时在注释中解释为什么必须按照这个顺序进行析构。 - Gerald
@Gerald,设计显然必须依赖于构建的顺序。使用智能指针不会在顺序上创建新的依赖关系。它只是依赖于销毁顺序与构建顺序相反,这是语言内置的规则(除非你通过使用非智能指针来强制改变事物,正如OP所做的那样)。 - JSF
@JSF - 如果 ctor/dtor 的顺序像这样很重要,我宁愿明确地指定它,而不是依赖成员顺序。是的,规则已经内置于语言中,但这并不意味着你想依赖它来方便自己。特别是在团队环境中,这种设计只会给你带来麻烦。也就是说,有一天可能会出现一个编码标准,要求成员按字母顺序声明,甚至可能使用工具将代码提升到标准级别,这将导致错误,并且谁知道需要多长时间才能弄清楚发生了什么。(有过类似经历。) - Gerald
如果它们真的被命名为A和B,那么他就没问题了,但我有点怀疑 :) - Gerald
3个回答

7
遵循RAII原则。
Foo::a 更改为 std::unique_ptr<A>。 从 ~Foo 中删除 delete
现在,它将在 ~Foo 结束后以及 b.~Bar() 后被销毁。
请注意,成员按声明顺序创建,相反的顺序销毁。(初始化列表中的顺序不会改变创建顺序,这是出人意料的)。所以,如果您想让 ab 存活时间更长,只需先声明它。

1
创建一个中间类。
 class PreFoo 
 {
   A* a:
    ...
 };

 class Foo : public PreFoo
 {
   B b;
   ...
 };

你需要将 a 设为 protected 或 public,我建议使用私有继承以匹配原始设计。 - Neil Kirk

-2

编辑:更正错误(感谢 @Yakk)

你需要在 Foo 中将 B 设为指针,这样你就可以控制析构函数中的删除顺序;或者将 A 设为非指针,并将其放置在 Foo 的成员顺序中的 B 之前,这样它会在 B 之后被销毁,但这会使代码变得脆弱,因为成员声明的顺序现在很重要。在你的代码中加入注释。


正如我在问题中所述,我知道这一点,但想知道是否有可能不这样做。(我编辑了问题以使这个限制更清晰,以防它不够清楚) - Kritzefitz
@Kritzefitz 你需要对 Foo 进行一些修改,关键是要确定修改的内容。 - Neil Kirk
当然,我必须更改一些内容。但除了我排除的两个选项外,我不知道还有什么其他选择。也许有一些更优雅的解决方案,我不知道。 - Kritzefitz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接