return 的 C++ 析构函数

C++ destructor with return

在 C++ 中,如果我们将 class 析构函数定义为:

~Foo(){
   return;
}

调用此析构函数后,Foo 的对象将被销毁还是 从析构函数显式返回意味着我们永远不想销毁它。

我想使某个对象仅通过另一个对象的析构函数被销毁,即仅当另一个对象准备好被销毁时。

示例:

class Class1{
...
Class2* myClass2;
...
};

Class1::~Class1(){
    myClass2->status = FINISHED;
    delete myClass2;
}

Class2::~Class2(){
    if (status != FINISHED) return;
}

我在网上搜索过,但似乎找不到我的问题的答案。 我也尝试通过使用调试器逐步检查一些代码来自己解决这个问题,但无法得出结论性的结果。

does explicitly returning from the destructor mean that we don't ever want to destroy it.

没有。

析构函数是一个函数,因此您可以在其中使用 return 关键字,但这不会阻止对象的销毁,一旦进入析构函数,您就已经在销毁对象,因此任何想要阻止这种情况的逻辑必须在之前发生。

出于某种原因,我直觉上认为您的设计问题可以通过 shared_ptr 和自定义删除器来解决,但这需要有关上述问题的更多信息。

不,你不能阻止对象被return语句销毁,它只是意味着dtor主体的执行将在那个点结束。之后它仍然会被销毁(包括它的成员和基数),内存仍然会被释放。

你可能会抛出异常。

Class2::~Class2() noexcept(false) {
    if (status != FINISHED) throw some_exception();
}

Class1::~Class1() {
    myClass2->status = FINISHED;
    try {
        delete myClass2;
    } catch (some_exception& e) {
        // what should we do now?
    }
}

请注意,这确实是一个 糟糕 的想法。你最好重新考虑设计,我相信一定有更好的设计。抛出异常并不会阻止其基类和成员的销毁,只是可以得到Class2的dtor的处理结果。它能做什么还不清楚。

无论您多快return 退出析构函数,内部所有基于堆栈的对象都将被析构。如果您错过 delete 动态分配的对象,那么就会 故意 内存泄漏。

这就是 move-constructors 的工作原理。移动 CTOR 只会占用原始对象的内存。原始对象的析构函数简单地 wont/cant 调用 delete.

根据 C++ 标准(12.4 析构函数)

8 After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see 12.6).

所以返回语句不会阻止调用析构函数的对象被销毁。

没有。 return 只是表示退出方法,并不会停止对象的销毁。

另外,你为什么要这样做?如果该对象分配在堆栈上并且您以某种方式设法停止销毁,那么该对象将存在于堆栈的回收部分中,该部分可能会被下一个函数调用覆盖,这可能会覆盖整个对象内存并创建未定义的行为.

同样,如果对象是在堆上分配的,并且您设法防止了破坏,那么您将发生内存泄漏,因为调用 delete 的代码会假定它不需要保留一个指向对象的指针,而它实际上仍然存在并且占用了没有人引用的内存。

~Foo(){
   return;
}

的意思完全一样:

~Foo() {}

它类似于void函数;在没有 return; 语句的情况下到达结尾与在结尾处有 return; 相同。

析构函数包含在 Foo 的销毁过程已经开始时执行的操作。在不中止整个程序的情况下中止销毁过程是不可能的。

您可以创建一个新方法来创建对象 "commit suicide" 并将析构函数保持为空,这样就可以完成您想做的工作:

Class1::~Class1()
{
    myClass2->status = FINISHED;
    myClass2->DeleteMe();
}

void Class2::DeleteMe()
{
   if (status == FINISHED)
   {
      delete this;
   }
 }

 Class2::~Class2()
 {
 }

[D]oes explicitly returning from the destructor mean that we don't ever want to destroy it?

没有。早期的 return(通过 return;throw ...)仅意味着析构函数主体的其余部分未被执行。基础和成员仍然被销毁,它们的析构函数仍然 运行,参见 [except.ctor]/3

For an object of class type of any storage duration whose initialization or destruction is terminated by an exception, the destructor is invoked for each of the object's fully constructed subobjects...

有关此行为的代码示例,请参见下文。


I want to make it so that a certain object is destroyed only through another objects destructor i.e. only when the other object is ready to be destroyed.

听起来这个问题的根源在于所有权问题。删除 "owned" 对象只有在父对象被一个非常常见的习语破坏并通过(但不限于)之一实现时才删除;

  • 组合,是自动成员变量(即"stack based")
  • 一个std::unique_ptr<>表示动态对象的独占所有权
  • A std::shared_ptr<> 表示动态对象的共享所有权

鉴于 OP 中的代码示例,std::unique_ptr<> 可能是合适的替代方案;

class Class1 {
  // ...
  std::unique_ptr<Class2> myClass2;
  // ...
};

Class1::~Class1() {
    myClass2->status = FINISHED;
    // do not delete, the deleter will run automatically
    // delete myClass2;
}

Class2::~Class2() {
    //if (status != FINISHED)
    //  return;
    // We are finished, we are being deleted.
}

我注意到示例代码中的 if 条件检查。它暗示状态与所有权和生命周期相关联。它们不是一回事;当然,您可以将达到某个状态的对象绑定到它的 "logical" 生命周期(即 运行 一些清理代码),但我会避免直接 link 到对象的所有权。重新考虑这里涉及的一些语义可能是一个更好的主意,或者允许 "natural" 构造和破坏来指示对象的开始和结束状态。

旁注;如果您必须检查析构函数中的某些状态(或断言某些结束条件),throw 的一种替代方法是在不满足该条件时调用 std::terminate(使用一些日志记录)。这种方法类似于标准行为和结果,当由于已经抛出的异常而展开堆栈时抛出异常时的结果。当 std::thread 以未处理的异常退出时,这也是标准行为。


[D]oes explicitly returning from the destructor mean that we don't ever want to destroy it?

否(见上文)。以下代码演示了此行为; linked here and a dynamic version需要noexcept(false)以避免std::terminate()被调用

#include <iostream>
using namespace std;
struct NoisyBase {
    NoisyBase() { cout << __func__ << endl; }
    ~NoisyBase() { cout << __func__ << endl; }
    NoisyBase(NoisyBase const&) { cout << __func__ << endl; }
    NoisyBase& operator=(NoisyBase const&) { cout << __func__ << endl; return *this; }    
};
struct NoisyMember {
    NoisyMember() { cout << __func__ << endl; }
    ~NoisyMember() { cout << __func__ << endl; }
    NoisyMember(NoisyMember const&) { cout << __func__ << endl; }
    NoisyMember& operator=(NoisyMember const&) { cout << __func__ << endl; return *this; }    
};
struct Thrower : NoisyBase {
    Thrower() { cout << __func__ << std::endl; }
    ~Thrower () noexcept(false) {
        cout << "before throw" << endl;
        throw 42;
        cout << "after throw" << endl;
    }
    NoisyMember m_;
};
struct Returner : NoisyBase {
    Returner() { cout << __func__ << std::endl; }
    ~Returner () noexcept(false) {
        cout << "before return" << endl;
        return;
        cout << "after return" << endl;
    }
    NoisyMember m_;
};
int main()
{
    try {
        Thrower t;
    }
    catch (int& e) {
        cout << "catch... " << e << endl;
    }
    {
        Returner r;
    }
}

有以下输出;

NoisyBase
NoisyMember
Thrower
before throw
~NoisyMember
~NoisyBase
catch... 42
NoisyBase
NoisyMember
Returner
before return
~NoisyMember
~NoisyBase

当然不是。 'return'的显式调用100%相当于析构函数执行后隐式返回

因此,正如所有其他人指出的那样,return 不是解决方案。

我要补充的第一件事是,您通常不应该为此担心。除非你的教授明确要求。 如果您不相信外部 class 只会在正确的时间删除您的 class,那将是非常奇怪的,而且我认为没有其他人看到它。 如果指针被传递,指针很可能是shared_ptr/weak_ptr,让它在适当的时候摧毁你的class。

但是,嘿,如果我们学到了一些东西(不要在截止日期前浪费时间!),想知道我们将如何解决一个奇怪的问题,如果它出现了,这很好。

那么解决方案呢?如果你至少可以相信 Class1 的析构函数不会过早销毁你的对象,那么你可以将 Class2 的析构函数声明为私有的,然后将 Class1 的析构函数声明为 Class2 的友元,如下所示:

class Class2;

class Class1 {

    Class2* myClass2;

public:
    ~Class1();
};

class Class2 {
private:
        ~Class2();

friend Class1::~Class1();
};

Class1::~Class1() {
    delete myClass2;
}

Class2::~Class2() {
}

作为奖励,您不需要 'status' 标志;这很好——如果有人想要这么糟糕地来搞砸你,为什么不在其他任何地方将状态标志设置为 FINISHED,然后调用 delete

这样,您就可以实际保证对象可以在 Class1 的析构函数中销毁。

当然,Class1 的析构函数可以访问 Class2 的所有私有成员。这可能并不重要——毕竟,无论如何,Class2 即将被摧毁!但如果确实如此,我们可以想出更复杂的方法来解决它;为什么不。例如:

class Class2;

class Class1 {
private:
    int status;

    Class2* myClass2;

public:
    ~Class1();
};

class Class2Hidden {
private:
      //Class2 private members
protected:
    ~Class2Hidden();
public:
    //Class2 public members
};

class Class2 : public Class2Hidden {
protected:
        ~Class2();

friend Class1::~Class1();
};

Class1::~Class1() {
    delete myClass2;
}

Class2Hidden::~Class2Hidden() {
}

Class2::~Class2() {
}

这样 public 成员在派生的 class 中仍然可用,但私有成员实际上是私有的。 ~Class1 将只能访问 Class2 的私有和受保护成员,以及 Class2Hidden 的受保护成员;在这种情况下,它只是析构函数。 如果您需要保护 Class2 的受保护成员不受 Class1 的析构函数的影响...有很多方法,但这实际上取决于您在做什么。

祝你好运!

对于这种情况,您可以使用删除运算符的特定于 class 的重载。 所以对于你 Class2 你可以这样

class Class2
{
    static void operator delete(void* ptr, std::size_t sz)
    {
        std::cout << "custom delete for size " << sz << '\n';
        if(finished)
            ::operator delete(ptr);
    }

    bool Finished;
}

那么如果你在删除之前将finished设置为true,就会调用实际的删除操作。 请注意,我还没有测试过它,我只是修改了我在这里找到的代码 http://en.cppreference.com/w/cpp/memory/new/operator_delete

Class1::~Class1()
{
    class2->Finished = true;
    delete class2;
}