返回一个对象会调用它的析构函数吗?

Posted

技术标签:

【中文标题】返回一个对象会调用它的析构函数吗?【英文标题】:Will returning an object call its destructor? 【发布时间】:2013-08-20 08:05:21 【问题描述】:

我有一个非常简单的类来处理回调。来电者要求经理回电。只要调用者持有回调对象,它就保持有效。然而,一旦对象死亡,它的析构函数会将 Manager 内的指针设置为 NULL,因此它知道在下次遇到它时将其丢弃。

...或者至少,这是我所追求的基本理念。

class Manager
public:
    void executeOnList()  ... 

    Callback requestCallback(Drawable * target)
          Drawable ** ptr = list.add(target);
          return Callback(ptr);  // <-- the point of interest
    
private:
    List list;
;


class Callback
    friend Manager;
private:
    Callback(Drawable ** targetPtr)
        drawablePtr = targetPtr;
    
public:
    ~Callback()
        (*drawablePtr) = NULL;  // <-- dtor of interest
    
private:
    Drawable ** drawablePtr;
;

我的问题是,Manager::requestCallback() 会在将结构返回给调用者之前调用Callback 的析构函数吗?

如果是这样,是否有任何方法可以防止这种情况发生,同时(或多或少)保持Callback 功能背后的基本理念?

【问题讨论】:

这取决于你是否得到return value optimization。在你的例子中,我希望你会。 @juanchopanza - 我已经读到了,虽然没有明确要求它似乎有风险...... 您对此无能为力(好吧,使用某些编译器您可以将其关闭,但这似乎是一种悲观)。您不应依赖 RVO 发生。 但也没有什么可担心的:如果省略了副本,则意味着少了一个构造,也少了一个破坏。 @juanchopanza - 虽然析构函数会将管理器中的状态归零,因此调用它一次或多次具有相同的效果。 RVO 似乎很容易确保,但我想知道它是否也适用于调试版本。我使用您提到的关于匹配构造函数和析构函数的想法进行了编辑。 【参考方案1】:

好吧,requestCallback返回后临时Callback对象会被销毁,这会导致Drawable *设置为NULL,复制出来的Callback对象也不起作用。

Move 确实有效,但它不是唯一的解决方案。其实这就像c++98中的auto-ptr,只是把资源从一个窃取到另一个。

【讨论】:

【参考方案2】:

堆栈上的每个对象都会在超出范围时自动销毁。也就是说,从概念上讲,临时对象在从函数返回时将超出范围。也就是说,当返回一个复制对象时,编译器可能会忽略它,在这种情况下,return 语句中的临时对象似乎根本不存在。但是,是否发生复制省略是一种优化,不能保证。

完全取决于析构函数似乎不适用于您的情况。但是,可能有用的是区分传递的临时对象和持有的对象(命名或指向)。基本思想是将指向指针视为对象拥有的资源,并仅在实际所有者被销毁(类似于std::unique_ptr&lt;T&gt;)或所有所有者都被销毁(类似于std::shared_ptr&lt;T&gt;)时重置它。假设你可以使用 r-value 引用,你可以得到这两种形式,否则你只能得到共享所有权。

以下是单一所有权逻辑的简要概述:

class Callback
    friend Manager;
private:
    Callback(Drawable ** targetPtr)
        drawablePtr = targetPtr;
    
public:
    Callback(Callback&& other):
        drawablePtr(other.drawablePtr) 
        other.drawablePtr = 0;
    
    ~Callback()
        if (drawablePtr) 
            (*drawablePtr) = 0;
        
    
private:
    Drawable ** drawablePtr;
;

如果您不能使用 r 值语义,实际上您仍然可以使用相同的逻辑,但存在创建副本时从命名对象意外窃取“资源”的风险。使用移动构造函数可以避免这种风险。

【讨论】:

哦,这很聪明。我不得不重新阅读复制构造函数几次,然后才能理解它背后的逻辑。这会很好地工作,谢谢! @Clairvoire:请注意,“复制构造函数”实际上是一个移动构造函数。它也可以使用复制构造函数工作,尽管语义有些不寻常和危险,这就是为什么我在窃取资源是正常的情况下使用移动构造函数的原因。【参考方案3】:

您可以添加移动构造函数 (c++11) 或复制析构函数:

class Callback
    friend Manager;
private:
    explicit Callback(Drawable** targetPtr) : drawablePtr(targetPtr) 
    // C++11
    Callback(Callback&& rhs) : drawablePtr(rhs.drawablePtr)  rhs.drawablePtr = NULL; 
    // else
    Callback(Callback& rhs) : drawablePtr(rhs.drawablePtr)  rhs.drawablePtr = NULL; 
public:
    ~Callback()
        if (drawablePtr)  (*drawablePtr) = NULL; 
    
private:
    Drawable ** drawablePtr;
;

【讨论】:

我完全忘了检查指针的有效性,很好理解!我正在使用 VS2010,现在 C++11 已经过时了。也许我会很幸运,无论如何它都会有移动构造函数

以上是关于返回一个对象会调用它的析构函数吗?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在调用它的析构函数后我可以访问这个堆栈分配的对象? [复制]

为啥C++里面,析构函数会被调用两次

虚析构函数

cpp中的析构函数会自动调用吗?即使析构函数没有提及非动态变量,它们是不是也会被删除?

为啥自动对象的析构函数被调用两次?

C++中,子类会继承父类的虚函数表!对于父类的析构函数(虚函数) 也会继承吗?