带返回的C ++析构函数

时间:2016-05-17 10:26:37

标签: c++ c++11 return c++14 destructor

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

~Foo(){
   return;
}

在调用此析构函数时,Foo的对象将被销毁或完成 从析构函数显式返回意味着我们不想破坏它。

我想这样做,以便某个对象只能通过另一个对象析构函数销毁,即只有当另一个对象准备被销毁时才会被销毁。

示例:

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

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

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

我在网上搜索,似乎无法找到我的问题的答案。 我也尝试通过使用调试器逐步完成一些代码来解决这个问题,但无法得到确定的结果。

11 个答案:

答案 0 :(得分:45)

不,你不能阻止对象被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的处理结果。可以用它做什么仍然不明确。

答案 1 :(得分:24)

~Foo(){
   return;
}

表示完全相同:

~Foo() {}

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

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

答案 2 :(得分:17)

  

[D] oes明确地从析构函数返回意味着我们不想破坏它?

没有。提前返回(通过return;throw ...)仅表示析构函数的其余部分未执行。基础和成员仍然被破坏,他们的析构函数仍在运行,请参阅[except.ctor]/3

  

对于任何存储持续时间的类类型的对象,其初始化或销毁由异常终止,则为每个对象的完全构造的子对象调用析构函数...

请参阅下文,了解此行为的代码示例。

  

我想这样做,以便某个对象只能通过另一个对象析构函数销毁,即只有当另一个对象准备被销毁时才会被销毁。

听起来这个问题的根源在于所有权问题。仅在父母以非常常见的习语销毁并且使用(但不限于)之一实现时,删除“拥有”对象;

  • 组合,它是一个自动成员变量(即“基于堆栈”)
  • 表示动态对象的独占所有权的std::unique_ptr<>
  • 表示动态对象的共享所有权的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条件检查。它暗示了国家与所有权和生命关系密切相关。它们并非都是一样的;当然,你可以将对象达到某个状态到它的“逻辑”生命周期(即运行一些清理代码),但我会避免直接链接到对象的所有权。重新考虑这里涉及的一些语义或允许“自然”构造和破坏来决定对象的开始和结束状态可能是一个更好的主意。

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

  

[D] oes明确地从析构函数返回意味着我们不想破坏它?

否(见上文)。以下代码演示了此行为; linked heredynamic 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

答案 3 :(得分:8)

根据C ++标准(12.4 Destructors)

  

8执行析构函数体后破坏任何析构函数   在body中分配的自动对象,类X的析构函数   为X的直接非变量非静态数据调用析构函数   成员,X的直接基类的析构函数,如果X是   最派生类的类型(12.6.2),它的析构函数调用   X的虚拟基类的析构函数。所有的析构函数都被称为   好像它们是用限定名称引用的,即忽略   更多派生类中任何可能的虚拟覆盖析构函数。   基地和成员按完成的相反顺序销毁   他们的构造函数(见12.6.2)。 a中的返回声明(6.6.3)   析构函数可能不会直接返回调用者;之前   将控制权转移给呼叫者,成员的析构者   和基地被称为。调用数组元素的析构函数   按其构造的相反顺序(见12.6)。

因此,返回语句不会阻止调用析构函数的对象被破坏。

答案 4 :(得分:7)

  

明确地从析构函数返回意味着我们不想破坏它。

没有。

析构函数是一个函数,因此您可以在其中使用return关键字,但是这不会阻止对象的破坏,一旦您在析构函数内部,您已经在摧毁对象,所以任何想要防止以前必须发生的逻辑。

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

答案 5 :(得分:1)

无论你在析构函数中return多久,内部所有基于堆栈的对象都将被破坏。如果您错过了delete动态分配的对象,那么会有故意内存泄漏。

这就是 move-constructors 如何运作的全部想法。移动CTOR只会占用原始对象的内存。原始对象的析构函数根本不会/不能调用delete

答案 6 :(得分:1)

没有。 return只是意味着退出方法,它不会停止对象的破坏。

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

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

答案 7 :(得分:1)

你可以制作一种新方法来使对象自杀&#34;并保持析构函数为空,所以这样的事情将完成你想做的工作:

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

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

 Class2::~Class2()
 {
 }

答案 8 :(得分:1)

当然不是。显式调用&#39; return&#39;在执行析构函数后,ist 100%等效于隐式返回。

答案 9 :(得分:1)

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

我要补充的第一件事是你通常不应该担心这一点。除非你的教授明确要求。 如果你不相信外部类只能在合适的时间删除你的课程,那将是非常奇怪的,我认为没有其他人看到它。 如果指针被传递,指针很可能是shared_ptr / weak_ptr,并让它在适当的时刻销毁你的类。

但是,嘿,如果我们学到了什么(并且在截止日期之前不浪费时间,我们很想知道如果它出现了,我们将如何解决一个奇怪的问题!)

那么解决方案呢?如果你至少可以信任Class1的析构函数而不是过早地破坏你的对象,那么你可以将Class2的析构函数声明为private,然后将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() {
}

这样,公共成员仍然可以在派生类中使用,但私有成员实际上是私有的。 ~Class1只能访问Class2的私有和受保护成员,以及Class2Hidden的受保护成员;在这种情况下,它只是析构函数。 如果你需要保护Class2的受保护成员免受Class1的析构函数的攻击......有办法,但这实际上取决于你在做什么。

祝你好运!

答案 10 :(得分:0)

对于这种情况,您可以使用delete运算符的特定于类的重载。 所以对你来说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;
}

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

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