Hubert Wang

I am
Hubert Wang

Wechat Official Account
Find fun things here!

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
可自由转载

1417
TOC
Comments
Write a Comment