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

发布于 2024-12-23 17:07:48 字数 3535 浏览 0 评论 0原文

代码是:

#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;
}

结果是:

调用了构造函数 Picture::Picture()
图片 p 数 1
调用复制构造函数 Picture::Picture(const Picture&)
图片 p 数 1
Frame_Pic::Frame_Pic(const Picture& orig) 调用
图片::图片(P_Node* p_node) 调用
析构函数 Picture::~Picture() 被调用
减少1前的图片p计数
减0后图片p计数
已删除
析构函数 Picture::~Picture() 被调用
减少2之前的图片p计数
析构函数 Picture::~Picture() 被调用
减少1前的图片p计数
减0后图片p计数
已删除

我之前问过一个关于这段代码的内存管理的问题,但是在了解答案之后,我仍然对析构函数和复制构造函数有疑问。根据我的理解,Picture temp = frame(my_pic) 将调用复制构造函数。

那么问题来了:

  1. 为什么在Picture temp = frame(my_pic)之后不调用复制构造函数
  2. ,为什么又调用析构函数呢?
  3. Picture Frame(const Picture&pic)中,如果调用该函数,会调用复制构造函数吗?我相信是这样,因为它按值返回“图片”。
  4. 如果我将Pictureframe(const Picture& pic)更改为Pictureframe(Picture p),调用函数时复制构造函数会调用两次吗?
  5. 什么时候会调用复制构造函数?当函数按值返回类时会发生这种情况吗?那么类什么时候按值传递给函数呢?
  6. 析构函数什么时候被调用?是在每次变量的生命周期结束时吗?这是否意味着如果我按值将变量传递给函数,则其析构函数将在函数执行后被调用?

我现在对复制构造函数和析构函数感到困惑,特别是当我有一个带有返回值的函数并且某些参数全部按值传递时。

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

The code is:

#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;
}

The result is:

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

I previously asked a question about memory management of this code, but after understanding the answers, I still have a problem with the destructor and the copy constructor. In my understanding, Picture temp = frame(my_pic) will call the copy constructor.

Here comes the question:

  1. Why isn't the copy constructor called after Picture temp = frame(my_pic)
  2. and why is the destructor called?
  3. In Picture frame(const Picture& pic), will the copy constructor be called if the function is called? I believe so, because it returns a 'Picture' by value.
  4. If I change Picture frame(const Picture& pic) to Picture frame(Picture p) will the copy constructor called twice when the function is called?
  5. When will the copy constructor be called? Will it happen when the class is returned by a function by value? When then class is passed to a function by value?
  6. When will the destructor be called? Is it when each time a variable's lifetime is ended? Does that mean if I pass a variable to a function by value, its destructor will be called after the functions execution?

I'm messed up with the copy constructor and the destructor right now, especially when I have a function with a return value, and some parameters all passed by values.

Also, will anyone help me to write a comment on each line of the output strings? That would be very helpful.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

晨光如昨 2024-12-30 17:07:48

回答你的问题。

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

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

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

  4. 是的,如果您将 frame 的参数更改为按值传递,但如果使用不是引用泛左值的表达式来初始化该参数,则可能会生成额外的复制构造函数调用现有对象,参数可以直接使用该表达式进行初始化,并且复制被省略。

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

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

In answer to your questions.

  1. The copy constructor isn't called after the statement Picture temp = frame(my_pic); because you don't have any statements that cause any copies after that statement.

  2. The three destructors for Picture are called to destroy (in order): temp, p in the Frame_Pic pointed to by temp.p and my_pic. Your compiler has avoided generating any other temporary Picture objects.

  3. Yes, a copy constructor may be called to initialize the return value of Picture frame(const Picture& pic) but the compiler is allowed (and does in the case) to eliminate the copy and initialize the return value directly from the return expression.

  4. Yes, an additional copy constructor call may be generated if you change the parameter for frame to be passed by value but if the parameter is initialized with an expression that isn't a glvalue referring to an existing object the argument might be initialized directly with that expression and the copy elided.

  5. A copy constructor is called whenever an object of class type is actually copied. This may be when being passed to a function or returned from a function but sometimes compilers are allowed to omit unnecessary copies in these scenarios.

  6. Yes, a destructor is called whenever an object of class type is destroyed. This is true for named variables and temporaries generated by the compiler. It is possible to end an object's lifetime without calling a destructor, e.g. my re-using its memory for another object, but this is very much a special case.

蒲公英的约定 2024-12-30 17:07:48

每当您认为可能或应该调用复制构造函数时,不一定会调用它:

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

  1. 当对象按值返回时
  2. 当对象作为参数通过值传递(到函数)时
  3. 当物体被抛出时
  4. 当物体被捕获时
  5. 当一个对象被放置在大括号括起来的初始值设定项列表中时

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

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

来自 Wikipedia。

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

The copy constructor will not necessarily be called whenever you think it might or should be called:

The following cases may result in a call to a copy constructor:

  1. When an object is returned by value
  2. When an object is passed (to a function) by value as an argument
  3. When an object is thrown
  4. When an object is caught
  5. When an object is placed in a brace-enclosed initializer list

These cases are collectively called copy-initialization and are equivalent to: T x = a;

It is however,
not guaranteed that a copy constructor will be called in these cases,
because the C++ Standard allows the compiler to optimize the copy away
in certain cases, one example being the return value optimization
(sometimes referred to as RVO).

From Wikipedia.

The destructor for anything that's on the stack is called when it goes out of scope.

与酒说心事 2024-12-30 17:07:48

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

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

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

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

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

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

不。您没有创建副本,而是传递了对副本所在位置的引用并创建了一个新副本,编译器将在返回时优化该副本。

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

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

5) 什么时候会调用复制构造函数?当函数按值返回类时会发生这种情况吗?那么什么时候类通过值传递给函数呢?

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

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

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

Note: In all answers stating that the copy constructor will be called it is possible that it won't be because the compiler did some optimization.

1) Why isn't the copy consturctor called after Picture temp = frame(my_pic)?

Picture temp = frame(my_pic); is last line before the return statement, therefore all that happens after it is the program is torn down (destructors called, stack and heap cleared) and ends.

2) Why is the destructor called?

The destructor (in each case here) is being called because the program closed. Note: Although this does happen at the end of the program, it does not mean that you shouldn't cleanup after yourself!

3) In Picture frame(const Picture& pic), will the copy constructor be called if the function is called?

No. You didn't make a copy you passed a reference to where one is and created a new one and the compiler will optimize out the copy on the return.

4) If I change Picture frame(const Picture& pic) to Picture frame(Picture p), will the copy constructor be called twice when the function is called?

No. It may be called when you enter the function but the compiler will optimize out the copy in the return.

5) When will the copy constructor be called? Will it happen when the class is returned by a function by value? When then class is passed to a function by value?

The copy constructor will be called in both cases.

6) When will the destructor be called? when each time the variable's lifetime is ended? Does that mean if I pass a variable to a function by value, its destructor will be called after the function's execution?

When the destructor will be called when the object is destroyed. That could be because you destroyed it or the function containing it returns (ends) and its variables/objects are removed from the stack or in some cases (at the end of the program) from the heap.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文