在从函数返回期间,复制构造函数的目的是什么?[复制]

What is the purpose of copy constructor during return from a function? [duplicate]

提问人:Preetom Saha Arko 提问时间:2/24/2023 最后编辑:Preetom Saha Arko 更新时间:2/24/2023 访问量:142

问:

#include <iostream>
using namespace std;

class samp
{
    int *p;
    int len;
    int idx;  // idx denotes the number of elements currently in the array (allocated by p)

    public:
    samp()  
    {
        len=0;
        p=NULL;
        idx=0;
        cout<<"inside default constructor"<<endl;
    }
    samp(int len)
    {
        this->len=len;
        p=new int[len];
        idx=0;
        cout<<"inside parameterized constructor, p="<<p<<endl;
    }
    samp(const samp &s)
    {
        len=s.len;
        idx=s.idx;
        
        p = new int[len];
        for(int i=0;i<idx;i++){
            p[i]=s.p[i];
        }
        cout<<"inside copy constructor, p="<<p<<endl;
    }
    ~samp()
    {
        cout<<"inside destructor, clearing memory "<<p<<endl;
        delete[] p;
        p=NULL;

    }
    void insert(int a)
    {
        if(idx<len){  // if there is still some place left in the array
            p[idx]=a;
            idx++;
        }
    }
    void print()
    {
        for(int i=0;i<idx;i++){
            cout<<p[i]<<" ";
        }
        cout<<endl;
    }
    
};

samp f()
{
    samp s(3);
    int a;
    for(int i=0;i<3;i++){
        cin>>a;
        s.insert(a);
    }
    cout<<"now we will return an object from a function"<<endl;
    return s;
}

int main()
{
    samp s1;
    cout<<"now we will try to return an object from a function"<<endl;
    s1 = f();
    cout<<"function returned"<<endl;
    s1.print();
    return 0;
}

我得到以下输出和双重释放错误,因为相同的内存被释放了两次。由于复制省略,此处未调用复制构造函数。

inside default constructor
now we will try to return an object from a function
inside parameterized constructor, p=0x5573aa164e80
now we will return an object from a function
inside destructor, clearing memory 0x5573aa164e80
function returned
0 0 -1441456112 
inside destructor, clearing memory 0x5573aa164e80

由于没有调用复制构造函数,因此我也得到了垃圾值。

当我用作编译器标志时,会调用复制构造函数,但双倍内存释放错误仍然存在。我得到这样的输出:-fno-elide-constructors

inside default constructor
now we will try to return an object from a function
inside parameterized constructor, p=0x10c008
now we will return an object from a function
inside copy constructor, p=0x10c040
inside destructor, clearing memory 0x10c008
inside destructor, clearing memory 0x10c040
function returned
85 23 19
inside destructor, clearing memory 0x10c040

Codeblocks 以某种方式设法打印存储在释放内存中的值,但相同的内存被释放了两次,我想避免这种情况。

在函数返回期间,析构函数在这里被调用了两次,很可能是因为它破坏了函数 f 的本地对象 s 一次,然后破坏了复制对象。

如果我们重载 = 运算符,双倍自由的问题就会消失。

samp& operator=(const samp &s)
{
    len=s.len;
    idx=s.idx;
        
    p = new int[len];
    for(int i=0;i<idx;i++){
        p[i]=s.p[i];
    }
        
    cout<<"assigning, new p="<<p<<endl;
    return *this;
}

然后我得到这样的输出:

inside default constructor
now we will try to return an object from a function
inside parameterized constructor, p=0x6bc008
now we will return an object from a function
inside copy constructor, p=0x6bc040
inside destructor, clearing memory 0x6bc008
assigning, new p=0x6bc078
inside destructor, clearing memory 0x6bc040
function returned
85 23 19
inside destructor, clearing memory 0x6bc078

我的问题是,如果复制构造函数无法解决内存位置被释放两次的问题,为什么在从函数返回对象时还要调用它?此外,= 运算符重载和复制构造函数的代码几乎相同。重载 = 运算符如何解决问题?

如果我从函数返回一个对象,那么很有可能在函数调用后将其分配给另一个对象。那么,每次我们从函数返回具有动态分配内存的对象时,我们是否需要重载 = 运算符?

我知道 STL 向量。在这种情况下,我需要知道复制构造函数的行为。

C++ 运算符重载 析构函数 复制构造函数

评论

0赞 πάντα ῥεῖ 2/24/2023
使用并让编译器通过 生成正确的编译器。std::vector<int> p;default
0赞 Preetom Saha Arko 2/24/2023
@πάνταῥεῖ 我知道 STL 向量。在这种情况下,我需要知道复制构造函数的行为。
3赞 Eljay 2/24/2023
该类未使用合成赋值运算符正确处理资源。通过以下方式禁用它:void operator=(samp const&) = delete;
3赞 no more sigsegv 2/24/2023
s1 = f();返回值 和 its 被逐位复制到一个带有赋值运算符的新实例中,现在两个实例都指向内存上的相同地址。当到达析构函数时,这两个实例都将尝试释放该内存。赋值运算符不调用 copy-constructor。在这种情况下,您还需要重载赋值运算符。f()pp
0赞 Preetom Saha Arko 2/24/2023
@Eljay 不明白你的意思。你能详细说明一下吗?

答:

4赞 Sam Varshavchik 2/24/2023 #1

您正在混淆和混合复制构造函数和赋值运算符。必须定义两者才能获得正确的 RAII,并正确处理分配的内存。

s1 = f();

这将调用赋值运算符。正是缺少赋值运算符导致了此处的双重释放。复制构造函数与它完全无关。

您的复制构造函数做得很好。从函数返回时,非省略副本始终调用复制构造函数。如果省略了副本,则不会复制任何内容,因此不涉及复制构造函数。

但是,无论是否省略了副本,这始终会导致调用赋值运算符。这是将返回的值分配给现有对象。句点。这需要调用赋值运算符。当将一个对象分配给另一个对象时,复制构造函数完全无关紧要。未能实现它导致了双重免费。

评论

0赞 Preetom Saha Arko 2/24/2023
如果我从函数返回一个对象,那么很有可能在函数调用后将其分配给另一个对象。那么,每次我们从函数返回具有动态分配内存的对象时,我们是否需要重载 = 运算符?
1赞 ChrisMM 2/24/2023
@PreetomSahaArko,如果您有动态内存,则通常应该有赋值运算符。stackoverflow.com/questions/4172722/what-is-the-rule-of-three
2赞 Sam Varshavchik 2/24/2023
每当默认运算符将导致未定义的行为(如果调用)时,运算符就应重载。如果确定该类的实例永远不会被分配给其他实例,则可以删除运算符,然后如果一个人的假设被证明是错误的,编译器就会吠叫。===