含指针变量的类需重新声明拷贝构造函数和赋值操作符
2014-02-21 15:31
197 查看
看下面一个表示string对象的类:
// 一个很简单的string类
class string
{
public:
string(const char *value);
~string();
... // 没有拷贝构造函数和operator=
private:
char *data;
};
string::string(const char *value)
{
if (value) {
data = new char[strlen(value) + 1];
strcpy(data, value);
}
else {
data = new char[1];
*data = '\0';
}
}
inline string::~string()
{ delete [] data; }
请注意这个类里没有声明赋值操作符和拷贝构造函数。这会带来一些不良后果。
如果这样定义两个对象:
string a("hello");
string b("world");
其结果就会如下所示:
a: data——> "hello\0"
b: data——> "world\0"
对象a的内部是一个指向包含字符串"hello"的内存的指针,对象b的内部是一个指向包含字符串"world"的内存的指针。如果进行下面的赋值:
b = a;
因为没有自定义的operator=可以调用,c++会生成并调用一个缺省的operator=操作符(见条款45)。这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作,对指针(a.data和b.data) 来说就是逐位拷贝。把b->data 指向 a->data指向的内存,但是b->data原来的空间却仍然存在,并没有释放!
这种情况下至少有两个问题。
第一,b曾指向的内存永远不会被删除,因而会永远丢失。这是产生内存泄漏的典型例子。
第二,现在a和b包含的指针指向同一个字符串,那么只要其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存。
stringa("hello"); //
定义并构造 a
{
// 开一个新的生存空间
stringb("world"); //
定义并构造 b
...
b =a;
// 执行 operator=
// 丢失b的内存
}
// 离开生存空间
// 调用b的析构函数
string c =a; //
c.data 的值不能确定
//
a.data 已被删除
例子中最后一个语句调用了拷贝构造函数,因为它也没有在类中定义,c++以与处理赋值操作符一样的方式生成一个拷贝构造函数并执行相同的动作:对对象里的指针进行逐位拷贝。这会导致同样的问题,但不用担心内存泄漏,因为被初始化的对象还不能指向任何的内存。比如上面代码中的情形,当c.data用a.data的值来初始化时没有内存泄漏,因为c.data没指向任何地方。不过,假如c被a初始化后,c.data和a.data指向同一个地方,那这个地方会被删除两次:一次在c被摧毁时,另一次在a被摧毁时。
牢记:只要类里有指针变量就得自己写拷贝构造函数和赋值函数,但是你确定用不着这些函数时,可以把这些函数做private声明而不去实现它,这就防止了会有人去调用它们,也防止了编译器去生成它们。
// 一个很简单的string类
class string
{
public:
string(const char *value);
~string();
... // 没有拷贝构造函数和operator=
private:
char *data;
};
string::string(const char *value)
{
if (value) {
data = new char[strlen(value) + 1];
strcpy(data, value);
}
else {
data = new char[1];
*data = '\0';
}
}
inline string::~string()
{ delete [] data; }
请注意这个类里没有声明赋值操作符和拷贝构造函数。这会带来一些不良后果。
如果这样定义两个对象:
string a("hello");
string b("world");
其结果就会如下所示:
a: data——> "hello\0"
b: data——> "world\0"
对象a的内部是一个指向包含字符串"hello"的内存的指针,对象b的内部是一个指向包含字符串"world"的内存的指针。如果进行下面的赋值:
b = a;
因为没有自定义的operator=可以调用,c++会生成并调用一个缺省的operator=操作符(见条款45)。这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作,对指针(a.data和b.data) 来说就是逐位拷贝。把b->data 指向 a->data指向的内存,但是b->data原来的空间却仍然存在,并没有释放!
这种情况下至少有两个问题。
第一,b曾指向的内存永远不会被删除,因而会永远丢失。这是产生内存泄漏的典型例子。
第二,现在a和b包含的指针指向同一个字符串,那么只要其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存。
stringa("hello"); //
定义并构造 a
{
// 开一个新的生存空间
stringb("world"); //
定义并构造 b
...
b =a;
// 执行 operator=
// 丢失b的内存
}
// 离开生存空间
// 调用b的析构函数
string c =a; //
c.data 的值不能确定
//
a.data 已被删除
例子中最后一个语句调用了拷贝构造函数,因为它也没有在类中定义,c++以与处理赋值操作符一样的方式生成一个拷贝构造函数并执行相同的动作:对对象里的指针进行逐位拷贝。这会导致同样的问题,但不用担心内存泄漏,因为被初始化的对象还不能指向任何的内存。比如上面代码中的情形,当c.data用a.data的值来初始化时没有内存泄漏,因为c.data没指向任何地方。不过,假如c被a初始化后,c.data和a.data指向同一个地方,那这个地方会被删除两次:一次在c被摧毁时,另一次在a被摧毁时。
牢记:只要类里有指针变量就得自己写拷贝构造函数和赋值函数,但是你确定用不着这些函数时,可以把这些函数做private声明而不去实现它,这就防止了会有人去调用它们,也防止了编译器去生成它们。
相关文章推荐
- Effective C++ 学习笔记:为含指针变量的类声明一个拷贝构造函数和一个赋值操作符
- 条款 11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 关于为什么要为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- Effective C++条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 为含指针变量的类声明一个拷贝构造函数和一个赋值操作符
- 对构造函数、拷贝构造函数和赋值操作符调用的简单回顾
- 也谈C++拷贝构造函数和赋值操作符
- C++中的拷贝构造函数和拷贝赋值操作符+const成员变量初始化(5)---《Effective C++》
- C++类中的4个特殊函数 - 缺省构造函数、拷贝构造函数、拷贝赋值操作符和析构函数
- 拷贝构造函数与赋值操作符
- 拷贝构造函数中是否可以调用重载后的赋值运算操作符
- 拷贝构造函数与拷贝赋值操作符
- C++类中的4个特殊函数 - 缺省构造函数、拷贝构造函数、拷贝赋值操作符和析构函数
- 为需要动态分配内存的类声明一个拷贝构造函数与一个赋值运算运算符