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!