何时调用复制构造函数和析构函数,为什么?

时间:2011-12-31 09:07:24

标签: c++ destructor copy-constructor

代码是:

#include <iostream>

class P_Node {
    friend class Picture;
protected:
    P_Node() : use(1) {}
    virtual ~P_Node() {}
private:
    int use;
};

class Picture {
    friend Picture frame(const Picture&);
public:
    Picture() : p(new P_Node) {
        std::cout << "Constructor\t" << "Picture::Picture()" << "\tcalled" << std::endl;
        std::cout << "Picture p count\t" << p->use << std::endl;
    }
    Picture(const Picture& orig) : p(orig.p) {
        std::cout << "Copy Constructor\t" << "Picture::Picture(const Picture&)" << "\tcalled" << std::endl;
        std::cout << "Picture p count\t" << p->use << std::endl;
        orig.p->use++;
    }
    ~Picture() {
        std::cout << "Destructor\t" << "Picture::~Picture()" << "\tcalled" << std::endl;
        std::cout << "Picture p count before decrease\t" << p->use << std::endl;
        if(--p->use == 0) {
            std::cout << "Picture p count after decrease\t" << p->use << std::endl;
            std::cout << "Deleted" << std::endl;
            delete p;
        }
    }
    Picture& operator=(const Picture& orig) {
        std::cout << "operator=\t" << "Picture& Picture::operator=(const Picture& orig)" << "\tcalled" << std::endl;
        std::cout << "Picture p count before decrease\t" << p->use << std::endl;
        orig.p->use++;
        if(--p->use == 0) {
            std::cout << "Picture p count after decrease\t" << p->use << std::endl;
            std::cout << "Deleted" << std::endl;
            delete p;
        }
        p = orig.p;
        return *this;
    }
private:
    Picture(P_Node* p_node) : p(p_node) {
        std::cout << "Picture::Picture(P_Node* p_node)\tcalled" << std::endl;
    }
    P_Node *p;
};

class Frame_Pic : public P_Node {
    friend Picture frame(const Picture&);
private:
    Frame_Pic(const Picture& pic) : p(pic) {
        std::cout << "Frame_Pic::Frame_Pic(const Picture& orig)" << "\tcalled" << std::endl;
    }
    Picture p;
};

Picture frame(const Picture& pic) {
    return new Frame_Pic(pic);
}

int main() {
    Picture my_pic;
    Picture temp = frame(my_pic);
    return 0;
}

结果是:

Constructor Picture::Picture()  called
Picture p count 1
Copy Constructor    Picture::Picture(const Picture&)    called
Picture p count 1
Frame_Pic::Frame_Pic(const Picture& orig)   called
Picture::Picture(P_Node* p_node)    called
Destructor  Picture::~Picture() called
Picture p count before decrease 1
Picture p count after decrease  0
Deleted
Destructor  Picture::~Picture() called
Picture p count before decrease 2
Destructor  Picture::~Picture() called
Picture p count before decrease 1
Picture p count after decrease  0
Deleted

我之前询问过有关此代码的内存管理的问题,但在理解了答案之后,我仍然遇到了析构函数和复制构造函数的问题。根据我的理解,Picture temp = frame(my_pic)将调用复制构造函数。

问题出现了:

  1. 为什么不在Picture temp = frame(my_pic)
  2. 之后调用复制构造函数
  3. 为什么析构函数被调用?
  4. Picture frame(const Picture& pic)中,如果调用该函数,是否会调用复制构造函数?我是这么认为的,因为它按值返回“图片”。
  5. 如果我将Picture frame(const Picture& pic)更改为Picture frame(Picture p),那么复制构造函数会在调用函数时调用两次吗?
  6. 何时调用复制构造函数?当函数按值返回类时会发生吗?何时通过值将类传递给函数?
  7. 什么时候会调用析构函数?是每次变量的生命周期结束时吗?这是否意味着如果我通过值将变量传递给函数,它的析构函数将在函数执行后被调用?
  8. 我现在搞乱了复制构造函数和析构函数,特别是当我有一个带有返回值的函数时,一些参数都是通过值传递的。

    另外,有人会帮我写输出字符串的每一行吗?这将非常有帮助。

3 个答案:

答案 0 :(得分:4)

回答你的问题。

  1. 在语句Picture temp = frame(my_pic);之后未调用复制构造函数,因为您没有任何语句在该语句之后导致任何副本。

  2. 调用Picture的三个析构函数来销毁(按顺序)tempp Frame_Pic指向的temp.pmy_pic。您的编译器已避免生成任何其他临时Picture对象。

  3. 是的,可以调用复制构造函数来初始化Picture frame(const Picture& pic)的返回值,但是允许编译器(在这种情况下也是如此)消除副本并直接从返回值初始化返回值表达

  4. 是的,如果您更改frame的参数以通过值传递,但是如果使用不是glvalue的表达式初始化参数,则可能会生成额外的复制构造函数调用现有对象可以使用该表达式直接初始化参数,并删除副本。

  5. 只要实际复制了类类型的对象,就会调用复制构造函数。这可能是在传递给函数或从函数返回时,但有时编译器可以在这些场景中省略不必要的副本。

  6. 是的,每当类类型的对象被销毁时都会调用析构函数。这适用于编译器生成的命名变量和临时变量。可以在不调用析构函数的情况下结束对象的生命周期,例如我将其内存重新用于另一个对象,但这是一个非常特殊的情况。

答案 1 :(得分:1)

只要您认为可能或应该调用复制构造函数,就不一定会调用它:

  

以下情况可能会导致对复制构造函数的调用:

     
      
  1. 按值返回对象时
  2.   
  3. 当一个对象以值作为参数传递给一个函数时
  4.   
  5. 投掷对象时
  6.   
  7. 捕获对象时
  8.   
  9. 当对象放置在括号内的初始化列表
  10. 中时         

    这些案例统称为复制初始化,相当于:T x = a;

         然而,

      不保证在这些情况下将调用复制构造函数,   因为C ++标准允许编译器优化副本   在某些情况下,一个例子是return value optimization   (有时也称为RVO)。

From Wikipedia.

当堆栈超出范围时,会调用堆栈中任何内容的析构函数。

答案 2 :(得分:0)

注意:在所有答案中都说明了复制构造函数将被调用,可能它不会是因为编译器做了一些优化。

  

1)为什么不在Picture temp = frame(my_pic)之后调用复制consturctor?

图片temp = frame(my_pic);是return语句之前的最后一行,因此在程序被删除之后发生的所有事情都被拆除(析构函数被调用,堆栈和堆清除)并结束。

  

2)为什么析构函数被调用?

因为程序已关闭,所以正在调用析构函数(在每种情况下都是这里)。注意:虽然这确实发生在程序结束时,但这并不意味着你不应该自己清理!

  

3)在Picture frame(const Picture& pic)中,如果调用函数,是否会调用复制构造函数?

没有。你没有制作副本,你传递了对一个人的位置的引用并创建了一个新副本,编译器将在返回时优化副本。

  

4)如果我将Picture frame(const Picture& pic)更改为Picture frame(Picture p),那么在调用函数时是否会调用复制构造函数两次?

没有。当您输入函数时可能会调用它,但编译器会在返回时优化副本。

  

5)何时会调用复制构造函数?当函数按值返回类时会发生吗?何时通过值将类传递给函数?

在这两种情况下都会调用复制构造函数。

  

6)什么时候会调用析构函数?每当变量的生命周期结束时?这是否意味着如果我通过值将变量传递给函数,它的析构函数将在函数执行后被调用?

当对象被销毁时将调用析构函数。这可能是因为你销毁它或者包含它的函数返回(结束)并且它的变量/对象从堆栈中删除,或者在某些情况下(在程序结束时)从堆中删除。