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

When are copy constructors and destructors called, and why?

本文关键字:调用 为什么 析构函数 构造函数 复制 何时      更新时间:2023-10-16

代码为:

#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 << "Constructort" << "Picture::Picture()" << "tcalled" << std::endl;
        std::cout << "Picture p countt" << p->use << std::endl;
    }
    Picture(const Picture& orig) : p(orig.p) {
        std::cout << "Copy Constructort" << "Picture::Picture(const Picture&)" << "tcalled" << std::endl;
        std::cout << "Picture p countt" << p->use << std::endl;
        orig.p->use++;
    }
    ~Picture() {
        std::cout << "Destructort" << "Picture::~Picture()" << "tcalled" << std::endl;
        std::cout << "Picture p count before decreaset" << p->use << std::endl;
        if(--p->use == 0) {
            std::cout << "Picture p count after decreaset" << 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 decreaset" << p->use << std::endl;
        orig.p->use++;
        if(--p->use == 0) {
            std::cout << "Picture p count after decreaset" << 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;
}

结果是:

<>之前构造函数Picture::Picture()被调用图片p计数1复制构造函数Picture::Picture(const Picture&)被调用图片p计数1Frame_Pic(const Picture& origin)被调用图片:图片(P_Node* P_Node)被调用析构函数Picture::~Picture()被调用图片计数前减少1图像p计数下降0后删除析构函数Picture::~Picture()被调用图片计数前减少2析构函数Picture::~Picture()被调用图片计数前减少1图像p计数下降0后删除

我之前问了一个关于这段代码的内存管理的问题,但是在理解了答案之后,我仍然对析构函数和复制构造函数有一个问题。在我的理解中,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. 什么时候调用析构函数?是每次变量生命周期结束的时候吗?这是否意味着如果我通过值传递一个变量给一个函数,它的析构函数将在函数执行后被调用?

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

还有,谁能帮我在每一行的输出字符串上写一个注释?那太有帮助了。

回答你的问题。

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

  2. Picture的三个析构函数被调用,在temp.pmy_pic所指向的Frame_Pic中依次销毁:tempp。您的编译器已避免生成任何其他临时Picture对象

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

  4. 是的,如果你将frame的形参改为按值传递,可能会生成一个额外的复制构造函数调用,但如果形参初始化时使用的表达式不是指向现有对象的glvalue,则实参可能直接使用该表达式初始化,并省略副本。

  5. 每当实际复制类类型的对象时,就调用复制构造函数。这可能是在传递给函数或从函数返回时,但有时编译器允许在这些情况下省略不必要的副本。

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

复制构造函数不一定在您认为可能或应该调用的时候调用:

下列情况可能导致调用复制构造函数:

  1. 当对象返回值
  2. 当一个对象作为参数值传递(给函数)
  3. 抛出对象时
  4. 对象被捕获时
  5. 当对象被放置在用大括号括起来的初始化列表中时
这些情况统称为复制初始化,相当于:T x = a;

然而,不能保证在这些情况下会调用复制构造函数,因为c++标准允许编译器将拷贝优化掉在某些情况下,返回值优化就是一个例子(有时称为RVO)。

从维基百科。

堆栈上的任何对象的析构函数在超出作用域时被调用。

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

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

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

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

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

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

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

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

。它可能在你进入函数时被调用,但编译器会在返回时优化出副本。

5)何时调用复制构造函数?当类由函数按值返回时,会发生这种情况吗?当然后类传递给一个函数的值?

两种情况下都将调用复制构造函数

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

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