C++拷贝构造函数
前几天修改Piotr Dollar的Edge Box代码,遇到很奇怪的问题,用构造的arrayf数据结构,新建一个变量之后,这个变量表现异常:
arrayf newE = E;
打印出来的乘法、if条件判断语句都出现错误。怀疑是指针指向的空间有问题。后来问了学长之后,学长说是定义的数据结构arrayf没有写拷贝构造函数(Copy Constructor)。
上述遇到的问题是典型的复制初始化导致内存资源混乱。
复制初始化
以下讨论中将用到的例子:
class CExample
{
public:
CExample(){pBuffer=NULL;nSize=0;}
~CExample(){delete[]pBuffer;}
void Init(intn){pBuffer=newchar[n];nSize=n;}
private:
char* pBuffer; // 类的对象中包含指针,指向动态分配的内存资源
int nSize;
};
// 这个类的主要特点是包含指向其他资源的指针,pBuffer指向堆中动态分配的一段内存空间。
int main(intargc,char*argv[])
{
CExampletheObjone;
theObjone.Init(40);
// 现在需要另一个对象,并将它初始化为theObjone
CExampletheObjtwo=theObjone;
...
}
语句"CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。
回顾一下此语句的具体过程:首先建立对象theObjtwo,并调用其构造函数,然后成员被复制初始化。
其完成方式是内存拷贝,复制所有成员的值。完成后,theObjtwo.pBuffer==theObjone.pBuffer。
即它们将指向同样的地方,指针虽然复制了,但所指向的空间并没有复制,而是由两个对象共用了。这样不符合要求,对象之间不独立了,并为空间的删除带来隐患。所以需要采用必要的手段来避免此类情况:可以在构造函数中添加操作来解决指针成员的这种问题。
所以C++语法中除了提供缺省形式的构造函数外,还规范了另一种特殊的构造函数:拷贝构造函数,一种特殊的构造函数重载。上面的语句中,如果类中定义了拷贝构造函数,在对象复制初始化时,调用的将是拷贝构造函数,而不是缺省构造函数。在拷贝构造函数中,可以根据传入的变量,复制指针所指向的资源。
拷贝构造函数的格式为:类名(const 类名& 对象名);//拷贝构造函数的原型,参数是常量对象的引用。由于拷贝构造函数的目的是成员复制,不应修改原对象,所以建议使用const关键字。
提供了拷贝构造函数后的CExample类定义为:
class CExample
{
public:
CExample(){pBuffer=NULL;nSize=0;}
~CExample(){delete[]pBuffer;}
CExample(const CExample&);//拷贝构造函数
void Init(int n){pBuffer=newchar[n];nSize=n;}
private:
char* pBuffer;//类的对象中包含指针,指向动态分配的内存资源
int nSize;
};
CExample::CExample(const CExample& RightSides)//拷贝构造函数的定义
{
nSize=RightSides.nSize;//复制常规成员
pBuffer=newchar[nSize];//分配内存
memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
}
这样,定义新对象,并用已有对象初始化新对象时,CExample(const CExample& RightSides)将被调用,而已有对象用别名RightSides传给构造函数,以用来作复制。
对象作为参数按值传递
当对象直接作为参数传给函数时,函数将建立对象的临时拷贝,这个拷贝过程也将调用拷贝构造函数。例如:
BOOL testfunc(CExample obj);
testfunc(theObjone); //对象直接作为参数。
BOOL testfunc(CExample obj)
{
//针对obj的操作实际上是针对复制后的临时拷贝进行的
}
对象作为返回值按值传递
还有一种情况,也是与临时对象有关:当函数中的局部对象作为返回值被返回给函数调者时,也将建立此局部对象的一个临时拷贝,拷贝构造函数也将被调用。
CTest func()
{
CTest theTest;
return theTest;
}
三种调用拷贝构造函数的归纳
以上列出了三种调用拷贝构造函数的情况,归纳如下:
1) 一个对象用于给另外一个对象进行初始化(常称为赋值初始化);
2) 一个对象作为函数参数,以值传递的方式传入函数体;
3) 一个对象作为函数返回值,以值传递的方式从函数返回;
对于第一种情况来说,初始化和赋值的不同含义是拷贝构造函数调用的原因。如果在后两种情况,不使用拷贝构造函数,就会导致一个指针指向已经被删除的内存空间。事实上,拷贝构造函数是由普通构造函数和赋值操作符共同实现的。
赋值重载
用下面例子说明:
int main(intargc,char*argv[])
{
CExample theObjone;
theObjone.Init(40);
CExample theObjthree;
theObjthree.init(60);
//现在需要一个对象赋值操作,被赋值对象的原内容被清除,并用右边对象的内容填充。
theObjthree=theObjone;
return0;
}
这里也用到了"="号,但与"复制初始化"中的例子并不同。"复制初始化"的例子中,"="在对象声明语句中,表示初始化。更多时候,这种初始化也可用圆括号表示。例如:CExample theObjthree(theObjone);。
而本例子中,"="表示赋值操作。将对象theObjone的内容复制到对象theObjthree,这其中涉及到对象theObjthree原有内容的丢弃,新内容的复制。
但"="的缺省操作只是将成员变量的值相应复制。由于对象内包含指针,将造成不良后果:指针的值被丢弃了,但指针指向的内容并未释放。指针的值被复制了,但指针所指内容并未被复制。
因此,包含动态分配成员的类除提供拷贝构造函数外,还应该考虑重载"="赋值操作符号。
classCExample
{
public:
CExample(){pBuffer=NULL;nSize=0;}
~CExample(){deletepBuffer;}
CExample(constCExample&);//拷贝构造函数
CExample&operator=(constCExample&);//赋值符重载
voidInit(intn){pBuffer=newchar[n];nSize=n;}
private:
char*pBuffer;//类的对象中包含指针,指向动态分配的内存资源
intnSize;
};
//赋值操作符重载
CExample&CExample::operator=(constCExample&RightSides)
{
if(this==&RightSides)//如果自己给自己赋值则直接返回
{return*this;}
nSize=RightSides.nSize;//复制常规成员
char* temp=newchar[nSize];//复制指针指向的内容
memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
delete[]pBuffer;//删除原指针指向内容(将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
pBuffer=temp;//建立新指向
return *this;
}
参考资料作者已不可考
参考资料来源:百度百科、CSDN
可自由转载