为什么在删除对象之前调用复制构造函数

Why is the copy constructor being called before deleting the object?

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

我有以下类,其中包括一个复制构造函数:

头文件:Fred.h

namespace foo {

class Fred {
private:
    int _x;
    int _y;

public:
    Fred();                         // Default constructor
    Fred(Fred const &other);        // Copy constructor
    Fred(int x, int y);             // Regular parameters
    ~Fred();                        // Destrcutor
};

}

实现文件:Fred.cpp

#include "Fred.h"
#include <iostream>
foo::Fred::Fred(){
    _x = 0;
    _y = 0;
    std::cout << "Calling the default constructorn";
}
foo::Fred::Fred(Fred const &other){
    _x = other._x;
    _y = other._y;
    std::cout << "Calling the copy constructor";
}
foo::Fred::Fred(int x, int y){
    _x = x;
    _y = y;
    std::cout << "Calling the convenience constructorn";
}
foo::Fred::~Fred(){
    std::cout << "Goodbye, cruel world!n";
}

当析构函数超出范围时,我希望看到它被调用,相反,复制构造函数被调用,然后析构函数被调用。为什么要创建副本?我是在泄露记忆吗?

using namespace foo;
int main(int argc, const char * argv[])
{

    {
        Fred f2 = *new Fred();
    } // I was expecting to see a destructor call only

    return 0;
}

这是因为您使用的是内存泄漏运算符*new

分配有new的对象永远不会自动删除;仅通过明确使用CCD_ 3。您的代码动态地分配一个对象,将其复制到f2,然后丢失指向该动态对象的唯一指针。

如果你只是想创建一个本地对象:

Fred f2;

当您实际需要动态分配时(换句话说,如果对象需要超过当前范围),请始终使用RAII对象,如智能指针,以避免内存泄漏。例如:

std::unique_ptr<Fred> f2(new Fred);   // C++11 - no "delete" needed
auto f2 = std::make_unique<Fred>();   // C++14 - no "new" or "delete" needed

是的,代码泄漏内存:new Fred()在堆上分配了一个对象,但代码不保存返回的指针,也不删除该对象。

调用复制构造函数的原因是f2的创建复制了参数,就像它是一样

Fred f2(*new Fred());

您可能想写的是:

Fred f2 = Fred();

由于Fred有一个用户定义的默认构造函数,您可以将其缩短为:

Fred f2;

创建副本是因为您正在复制由以下表达式创建的动态分配对象:

new Fred();

进入Fred f2:

Fred f2 = *new Fred(); // f2 is a copy of RHS object

永远不会调用动态分配对象的析构函数。这是内存泄漏。将只调用f2的析构函数。

请参阅此演示。

C++默认情况下在堆栈上分配变量。当你写一个像Foo* foo = new Foo;这样的句子时,你要做的是在堆栈上分配一个指针,该指针指向你在堆上分配的新对象
您的代码忘记释放指针指向的对象所使用的内存,因此该对象永远不会被释放,及其析构函数永远不会被调用

我建议你检查一下这个线程:我应该什么时候在C++中使用新关键字?

通过取消对堆上新创建的对象的引用并将其分配给Fred f2,您可以简单地调用复制构造函数。它与相同

Fred f1;
Fred f2 = f1;

此外,您"丢失"了指向堆上对象的指针,该指针不会自动删除-->内存泄漏


如果您没有使用RAII(但应该),则需要手动清除。看起来像:

using namespace foo;
int main(int argc, const char * argv[])
{

    {
        Fred* pF2 = new Fred();  // keep the pointer to manually delete
    } 
    // no destructor call even though scope is left --> delete manually
    delete pF2;
    pF2 = 0;
    return 0;
}