拷贝构造函数--内存被重复释放的隐形人

c++中拷贝构造函数的概念一直没有仔细看,原因是没有在这个上面栽过跟头,最近终于有幸花了一个下午找一个内存重复释放的错误,发现是拷贝构造函数。

看一下可以运行的没有问题的例子,是如何变成内存杀手的:

#include <string.h>
#include <iostream>
using namespace std;

class MyString
{
private:
    int length;
    char* value;
public:
    MyString(const char* str = NULL){
        length = 0;
        value = NULL;
        SetString(str);
    }
    ~MyString(){
        delete[] value;
    }
public:
    void SetString(const char* str){
        if(str != NULL){
            length = strlen(str);
            value = new char[length + 1];
            memcpy(value, str, length);
            value[length] = 0x00;
        }
    }
public:
    const char* GetString(){
        return (const char*)value;
    }
};

int main(){
    MyString str("winlin");
    cout << "expect: winlin, actual: " << str.GetString() << endl;
    sleep(5);
    return 0;
}

MyString是一个简单的字符串,构造函数为const char*,默认为NULL。调用时指定字符串,这个程序执行没有问题。

有时候是习惯使然,例如,标准库的用法:

std::string str("first");
str = "second";

直接赋值符合编程习惯。当然,c++也提供了支持,就是拷贝构造函数(这里是操作符“=”)。

确实,一不小心,因为我一看是string,觉得可以用等号赋值,就这么调用:

    MyString str("winlin");
    str = "hello world";
    cout << "expect: hello world, actual: " << str.GetString() << endl;

调用结果如下:

[winlin@dev6 temp]$ g++ error.cpp -o test;./test
expect: winlin, actual: 
*** glibc detected *** ./test: double free or corruption (fasttop): 0x000000000177f030 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3723475726]
./test[0x400b73]
./test[0x400a8e]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x372341ec5d]
./test[0x400939]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:02 12855167                           /home/winlin/temp/test
00601000-00602000 rw-p 00001000 fd:02 12855167                           /home/winlin/temp/test
0177f000-017a0000 rw-p 00000000 00:00 0                                  [heap]
3722c00000-3722c1e000 r-xp 00000000 fd:00 1048973                        /lib64/ld-2.12.so
3722e1e000-3722e1f000 r--p 0001e000 fd:00 1048973                        /lib64/ld-2.12.so
3722e1f000-3722e20000 rw-p 0001f000 fd:00 1048973                        /lib64/ld-2.12.so
3722e20000-3722e21000 rw-p 00000000 00:00 0 
3723400000-3723575000 r-xp 00000000 fd:00 1048979                        /lib64/libc-2.12.so
3723575000-3723775000 ---p 00175000 fd:00 1048979                        /lib64/libc-2.12.so
3723775000-3723779000 r--p 00175000 fd:00 1048979                        /lib64/libc-2.12.so
3723779000-372377a000 rw-p 00179000 fd:00 1048979                        /lib64/libc-2.12.so
372377a000-372377f000 rw-p 00000000 00:00 0 
3723800000-3723883000 r-xp 00000000 fd:00 1048992                        /lib64/libm-2.12.so
3723883000-3723a82000 ---p 00083000 fd:00 1048992                        /lib64/libm-2.12.so
3723a82000-3723a83000 r--p 00082000 fd:00 1048992                        /lib64/libm-2.12.so
3723a83000-3723a84000 rw-p 00083000 fd:00 1048992                        /lib64/libm-2.12.so
372e400000-372e416000 r-xp 00000000 fd:00 1049023                        /lib64/libgcc_s-4.4.4-20100726.so.1
372e416000-372e615000 ---p 00016000 fd:00 1049023                        /lib64/libgcc_s-4.4.4-20100726.so.1
372e615000-372e616000 rw-p 00015000 fd:00 1049023                        /lib64/libgcc_s-4.4.4-20100726.so.1
3730000000-37300e9000 r-xp 00000000 fd:00 2127370                        /usr/lib64/libstdc++.so.6.0.13
37300e9000-37302e9000 ---p 000e9000 fd:00 2127370                        /usr/lib64/libstdc++.so.6.0.13
37302e9000-37302f0000 r--p 000e9000 fd:00 2127370                        /usr/lib64/libstdc++.so.6.0.13
37302f0000-37302f2000 rw-p 000f0000 fd:00 2127370                        /usr/lib64/libstdc++.so.6.0.13
37302f2000-3730307000 rw-p 00000000 00:00 0 
7f5337727000-7f533772c000 rw-p 00000000 00:00 0 
7f5337740000-7f5337742000 rw-p 00000000 00:00 0 
7fff28628000-7fff2863d000 rw-p 00000000 00:00 0                          [stack]
7fff286af000-7fff286b0000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted (core dumped)

输出是一个空字符串,而不是期望的“hello world”,其次,还有一个内存错误“double free or corruption”。

是拷贝构造函数和操作符”=“造成的,编译器会添加默认的拷贝构造函数,上面的代码相当于:

    MyString str("winlin");
    // MyString anonymous("hello world");
    // str.length = anonymous.length;
    // str.value = anonymous.value;
    // auto delete anonymous(the value is deleted)
    // auto delete str(the value delete again)
    str = "hello world";

简单的方法,禁止掉拷贝构造函数和操作符”=“:

#include <string.h>
#include <iostream>
using namespace std;

class MyString
{
private:
    int length;
    char* value;
private:
    MyString(const MyString& o);
    MyString& operator= (const MyString& o);
public:
    MyString(const char* str = NULL){
        length = 0;
        value = NULL;
        SetString(str);
    }
    ~MyString(){
        delete[] value;
    }
public:
    void SetString(const char* str){
        if(str != NULL){
            length = strlen(str);
            value = new char[length + 1];
            memcpy(value, str, length);
            value[length] = 0x00;
        }
    }
public:
    const char* GetString(){
        return (const char*)value;
    }
};

int main(){
    MyString str("winlin");
    str = "hello world";
    cout << "expect: hello world, actual: " << str.GetString() << endl;
    sleep(5);
    return 0;
}

编译时将报错:

[winlin@dev6 temp]$ g++ disable_copy.cpp -o test
disable_copy.cpp: In function ‘int main()’:
disable_copy.cpp:12: error: ‘MyString& MyString::operator=(const MyString&)’ is private
disable_copy.cpp:39: error: within this context

或者提供正确的拷贝构造函数和操作符“=”:

#include <string.h>
#include <iostream>
using namespace std;

class MyString
{
private:
    int length;
    char* value;
public:
    MyString(const MyString& o){
        *this = o;
    }
    MyString& operator= (const MyString& o){
        if(this == &o){
            return *this;
        }
        SetString((const char*)o.value);
        
        return *this;
    }
public:
    MyString(const char* str = NULL){
        length = 0;
        value = NULL;
        SetString(str);
    }
    ~MyString(){
        delete[] value;
    }
public:
    void SetString(const char* str){
        if(str != NULL){
            length = strlen(str);
            value = new char[length + 1];
            memcpy(value, str, length);
            value[length] = 0x00;
        }
    }
public:
    const char* GetString(){
        return (const char*)value;
    }
};

int main(){
    MyString str("winlin");
    str = "hello world";
    cout << "expect: hello world, actual: " << str.GetString() << endl;
    sleep(5);
    return 0;
}

所以,凡是成员有指针(更精确点,在析构中释放该指针的)的类都需要写拷贝构造函数,否则一个不小心的调用就得拼命找bug!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

winlinvip

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值