如果没有为一个类声明construct function、copy construct function、copy assignment function、destruct function,编译器就会为之声明默认版本。
class Empty{
public:
Empty() {...}
Empty(const Empty &ths) {...}
~Empty() {...}
Empty& operator= (const Empty &rhs) {...}
};
注意:1、这些默认版本的函数都是public and inline;
2、只有当这些函数被调用时,才会被编译器创建出来。
然而,在某些情况下,需要自己实现这些函数,否则无法实现程序的本意。这就是C++中最容易出问题的地方,也恰恰是其迷人之处。这些情况包括:
1、涉及到资源分配;
2、涉及到const成员、引用等。
在《C++ Primer》中,第13章“复制控制”专门讲解了相关的内容。
所谓的资源分配就是指类的数据成员中有指针类型,这时候就需要进行深拷贝,而默认版本的copy construct 只能完整深拷贝。这时候就需要自己编写“复制控制”的相关函数。
对于const 数据成员,由于const属性,该成员不可以被更改,所以default copy construct是非法的;而引用,同样也不可以让其改指不同的对象。
如果有以上情况出现,而有没有自己定义复制控制,编译器就会拒绝编译。
还有一种情况编译器也会拒绝编译:基类将copy assignment操作符声明为private,编译器将拒绝为其派生类生成一个copy assignment操作符
一个小例子:
//myheader.h
#ifndef __MYHEADER_H
#define __MYHEADER_H
class A{
public:
A(int n);
A();
~A();
// A& operator= (const A &a);
private:
int **data;
int size;
};
#endif
//myheader.cpp
#include "myheader.h"
#include <stdio.h>
A::A(): data(NULL),size(0)
{}
A::A(int n)
{
size = n;
data = new int*[n];
for(int i=0; i<n; i++)
data[i] = NULL;
}
A::~A()
{
if(data)
for(int i=0; i<size; i++)
delete []data[i];
}
//test.cpp
int main()
{
A *a = new A[3];
A b(2);
for(int i=0; i<3; i++)
{
A c(2);
a[i] = c;
}
return 0;
}
这个小例子中,没有声明自己的copying assignment函数,所以系统会默认提供一个,但是本例中又涉及到了资源分配,而默认copying assignment只能无法实现深拷贝,这样程序就会出现问题。
在test.cpp文件中的for循环是为了将数组a初始化,可能达到目的吗?答案是否定的。在循环中,声明了一个对象c,然后用对象c初始化a[i],这时候a[i]的数据成员指针和c的数据 成员指针指向的是同一块内存区域。初始化完毕后,问题马上就出现了:由于c在块作用域里,其声明周期在一次循环后就结束,所以每次循环后,都会调用析构函数,释放动态分配的内存,这也意味着a[i]的数据成员指针指向的内存区域也被释放掉了!!!这显然是错误的!
挽救方案就是定义自己的copying assignment函数,实现深拷贝。
A& A::operator= (const A &a)
{
if(data != NULL){
for(int i=0; i<size; i++){
delete []data[i];
data[i] = NULL;
}
}
/**data = NULL;*/
size = a.size;
data = new int*[size];
for(int i=0; i<size; i++){
data[i] = NULL;
}
return *this;
}