2011-12-31 79 views
1

的代码是:什么时候复制构造函数和析构函数被调用,为什么?

#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. Picture frame(const Picture& pic),如果函数被调用,会调用复制构造函数吗?我相信是这样,因为它会以价值回报'图片'。
  4. 如果我将Picture frame(const Picture& pic)更改为Picture frame(Picture p),函数调用时复制构造函数会调用两次吗?
  5. 何时会调用复制构造函数?当这个类被函数按值返回时会发生吗?当然后类通过值传递给一个函数?
  6. 什么时候可以调用析构函数?每次变量的生命周期结束了吗?这是否意味着如果我通过值将一个变量传递给一个函数,它的析构函数将在函数执行后被调用?

我现在搞砸了复制构造函数和析构函数,特别是当我有一个带有返回值的函数和一些参数都通过值传递。

此外,任何人都可以帮我写每条输出字符串的评论吗?这将是非常有益的。

+3

注意,调用拷贝构造函数和赋值操作符可以自由编译器的优化被淘汰,即使他们有副作用。这是编译器被允许违反as-if规则的唯一两个。 (当然,编译器仍然需要不违反逻辑并将调用与ctors和dtors相匹配。) – sbi 2011-12-31 09:23:39

+0

@outis:否。返回表达式的类型为'Frame_Pic *'。'Frame_Pic'派生自'P_Node','P_Node *'通过私有构造函数隐式转换为'Picture',这很好,因为'frame'被声明为朋友。正如函数声明的那样,主体返回一个'Picture'。 – 2011-12-31 09:27:06

+0

@CharlesBailey所以代码本身并不是_wrong_,但我绝对不会称之为好的做法。 – bames53 2011-12-31 10:07:49

回答

3

在回答你的问题。

  1. 拷贝构造函数,因为你没有导致该声明之后的任何副本的任何声明不声明Picture temp = frame(my_pic);后调用。

  2. Picture三个析构函数被称为破坏(按顺序):temppFrame_Pic通过temp.pmy_pic指出。您的编译器已避免生成任何其他临时对象Picture

  3. 是的,可能会调用一个拷贝构造函数来初始化返回值Picture frame(const Picture& pic),但编译器被允许(并且在这种情况下)消除拷贝并直接从返回表达式初始化返回值。

  4. 是的,可如果更改frame按值传递,但是如果该参数与是不是指现有的对象可能的参数一个glvalue表达式初始化参数产生的额外拷贝构造函数调用直接用该表达式进行初始化,并且复制消失。

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

  6. 是的,只要类类型的对象被销毁,就会调用析构函数。这对于由编译器生成的命名变量和临时对象来说是正确的。可以在不调用析构函数的情况下结束对象的生命周期,例如我重新使用它的内存来处理另一个对象,但这非常特殊。

+0

关于答案1,如果我写'Picture a;图片b = a'图片B的复制生成器将被调用。那么为什么'图片temp = frame(my_pic)'不会调用它的复制构造函数?是不是'框架'生成一个'图片',并用它来调用'图片温度的复制构造? – shengy 2011-12-31 16:43:35

+0

@shengy:'Picture temp = frame(my_pic);'指定复制初始化,它可以_may_调用复制构造函数,但临时和复制构造函数可能会被忽略,在这里就是这种情况。你问为什么复制构造函数在_after_这个陈述之后被调用,这是一个不同的问题。 – 2011-12-31 16:47:47

1

的拷贝构造函数,只要你认为它可能或应该叫不一定叫:

以下情况可能会导致一个拷贝构造函数的调用:

  1. 当一个对象由返回值
  2. 当对象通过值传递(给函数)作为参数
  3. 当对象被抛出
  4. 当对象被捕获
  5. 当物体被放置在大括号内的初始化列表

这些情况统称副本初始化并且等同于:T x = a;

然而, 不能保证一个拷贝构造将在这些情况下被调用, 因为C++标准允许编译器优化副本远在某些情况下 ,一个例子是return value optimization (有时称为作为RVO)。

From Wikipedia.

任何东西的析构函数的栈上,当它超出范围时调用。

+0

但为什么当我使用语句'Picture temp = frame(my_pic)'时不调用Picture的副本构造函数? – shengy 2011-12-31 09:23:07

+0

[可能因为这个,在我的答案中提到]。(http://en.wikipedia.org/wiki/Return_value_optimization) – 2011-12-31 09:24:04

+0

@shengy:但它被调用,请参阅您发布的输出的第2行。 – 2011-12-31 09:24:38

0

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

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

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

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

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

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

不可以。您没有创建一个副本,您传递了一个引用,并创建一个新引用,编译器将优化返回副本。

4)如果我改变Picture frame(const Picture& pic)Picture frame(Picture p),将拷贝构造函数时两次调用该函数叫什么名字?

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

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

拷贝构造函数会在两种情况下被调用。

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

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

+0

'图片temp = frame(my_pic);'是_copy initialization_,它不一定会导致对复制构造函数的调用。在这种情况下,'Picture'的拷贝构造函数实际上只是在'Frame_Pic'的成员初始化列表中初始化时生成的:'[:]:p(pic){' – 2011-12-31 09:53:15

+0

@Charles Bailey Typo编辑答案,nice catch !谢谢。 – vdbuilder 2011-12-31 10:03:52

相关问题