找回密码
 立即注册
收起左侧

C++老鸟日记036 拷贝构造函数

2
回复
4781
查看
[复制链接]
累计签到:41 天
连续签到:1 天
来源: 原创 2018-9-30 15:07:01 显示全部楼层 |阅读模式

马上注册,查看详细内容!注册请先查看:注册须知

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本帖最后由 baizy77 于 2018-10-2 11:10 编辑

版权声明
---------------------------------------------------------------------------------------------------------------------
该文章原创于Qter开源社区(www.qter.org
作者: 女儿叫老白 (白振勇)
转载请注明出处!
---------------------------------------------------------------------------------------------------------------------
本套课程属于:《C++跨平台开发干货》系列课程。
-----------------------------------------------------------------------------


引言:
----------------------------------------------------------------------------
       之前我们多次提到过拷贝构造函数,但是我们仍然存在一些疑问:
1, 拷贝构造函数有什么作用?
2, 按位拷贝是什么意思?
3, 什么情况下,编译器才总是调用我们的拷贝构造函数,而不是执行位拷贝?
4, 什么情况下,编译器会创建默认的拷贝构造函数?反之,什么情况下,编译器不会自动创建默认的拷贝构造函数?
5,  如何防止按值传递对象?
今天我们来找一下这些问题的答案。
正文:
----------------------------------------------------------------------------
       一般情况下,如果我们没有编写类的拷贝构造函数,编译器就会执行按位拷贝:
代码清单:
----------------------------------------------------------------------------
// myclass.h
class CMyClass {
public:
CMyClass():m_nValue(0) {}
~CMyClass(){}
voidsetValue(int n) {m_nValue = n;}
intgetValue() {return m_nValue;}
private:
intm_nValue;
};

// main.cpp
int func(CMyClass mc) {
       int ret = mc.getValue();
returnret;
}

int main(int argc char*argv[]) {
       CMyClass myclass;
       int ret = func(myclass);
}
----------------------------------------------------------------------------
       在上述代码中,int ret = func(myclass)这一处代码,对于int func(CMyClass mc)函数来说,因为参数mc并没有使用引用,因此编译器在调用func()时,只是对myclass对象执行按位拷贝,也就是把myclass变量的内部成员数据原封不动的拷贝一份给新的临时变量,然后作为参数传递给func()。
       大家可能觉得奇怪,把旧对象的内部变量拷贝一份给新对象有什么问题吗?要回答这个问题,我们来看一种情况:
代码清单:
----------------------------------------------------------------------------
// myclass2.h
class CMyClass2 {
public:
CMyClass2():m_nValue (0), m_pBuffer(NULL) {}
~CMyClass2();
voidsetCount(int n);
intgetCount () {return m_nCount;}
private:
intm_nCount;
char*m_pBuffer;
};

// myclass2.cpp
void CMyClass2 ::setCount(int n) {
       if (n < 0) {
             return;
}
m_nCount= n;
if(NULL != m_pBuffer) {
       delete[] m_pBuffer;
}
if(m_nCount> 0 ) {
m_pBuffer = new char(m_nCount);
}
}
CMyClass2 ::~CMyClass2(){
       if (NULL != m_pBuffer) {
       delete[] m_pBuffer;
}
}

// main.cpp
int func(CMyClass2 mc) {
       int ret = mc.getCount ();
returnret;
}

int main(int argc char*argv[]) {
       CMyClass2 myclass;
       int ret = func(myclass);
}

----------------------------------------------------------------------------
       从上述代码可以看出,CMyClass2这个类拥有一个指针m_pBuffer,如果在ain()函数中调用fun()时编译器仍然按照按位拷贝,那么myclass与传入func()时编译器产生的临时对象的m_pBuffer成员将会指向同一块内存,而func()函数调用完成后,编译器产生的临时对象就会析构,看一下CMyClass2的析构函数就能知道,m_pBuffer所指向的内存将被释放,而这是我们不希望看到的。
       所以,由编译器负责执行按位拷贝传递参数的方法,在有些时候并不靠谱。那么课改怎么解决这个问题呢?方法就是提供类的拷贝构造函数。
代码清单:
----------------------------------------------------------------------------
// myclass3.h
class CMyClass3 {
public:
CMyClass3():m_nValue (0), m_pBuffer(NULL) {}
CMyClass3(constCMyClass3& right);
~CMyClass3();
voidsetCount (int n);
intgetCount () const {return m_nCount;}

constchar* getBuffer() const {return m_pBuffer};
private:
intm_nCount
char*m_pBuffer;
};

// myclass3.cpp
void CMyClass3::setCount(int n) {
       if (n < 0) {
             return;
}
m_nCount= n;
if(NULL != m_pBuffer) {
       delete[] m_pBuffer;
}
if(m_nCount> 0 ) {
m_pBuffer = new char(m_nCount);
}
}
CMyClass ::~CMyClass(){
       if (NULL != m_pBuffer) {
       delete[] m_pBuffer;
}
}
CMyClass3:: CMyClass3(constCMyClass3& right) {
       setCount(right.getCount());
       memcpy(m_pBuffer, right.getBuffer(), right.getCount());
}
----------------------------------------------------------------------------
       在CMyClass3中,我们提供了拷贝构造函数:
       CMyClass3(const CMyClass3&);
       它提供一个const引用的参数,参数类型与类本身一致。在拷贝构造函数内部,我们编写了一些特殊代码,除了将成员变量做相应的赋值之外,还做了开辟内存、复制内存的操作,而编译器提供的按位拷贝是无法完成这些操作的。
       大家可能注意到了,我们把int getCount()函数结尾增加了const限定符,表明该接口内部不会改变类的成员变量,这是因为拷贝构造函数内部要调用这个接口,而拷贝构造函数的入口参数传的是const引用,所以,getCount()接口需要定义为const。否则编译器会报错。
       那么,什么情况下编译器才总是调用我们的拷贝构造函数,而不是执行位拷贝呢?那就是当我们提供了类的拷贝构造函数的情况下,编译器就不再执行位拷贝,而是调用我们的拷贝构造函数了。
       那什么情况下,编译器会创建默认的拷贝构造函数?答案是按值传递,且我们没有写拷贝构造函数。所谓按值传递,指的是没有按照引用或者变量地址(指针)的方式传递参数。
       反之,什么情况下,编译器不会自动创建默认的拷贝构造函数?答案是:当我们声明了私有拷贝构造函数。也就是我们声明了拷贝构造函数,并且把它声明为private。
那么如何防止按值传递对象?答案是:我们声明私有的拷贝构造函数,甚至不用定义它。也就是仅声明,不用实现拷贝构造函数。

结语:
----------------------------------------------------------------------------
拷贝构造函数在我们进行软件开发的过程中非常重要,我们需要充分的利用上面讲到的一些原则或者特性来操作,根据我们不同的开发场景提供不同的编码实现。

参考资料
----------------------------------------------------------------------------
《C++编程思想》两卷合订本中文版(11章),(美) Bruce Eckel  Chuck Allison著

回复

使用道具 举报

累计签到:50 天
连续签到:1 天
2018-10-2 10:11:55 显示全部楼层
我把我阅读的总结哈。
位拷贝,也就是说的赋值了一份父类相同的值,如果有空间申请的话,那么析构会出现两次释放空间,不能独立的进行操作。
常对象的拷贝构造函数, 需要使用里面的成员变量, 使用的是常成员函数调用。   表达不是很清楚。   
回复 支持 反对

使用道具 举报

累计签到:41 天
连续签到:1 天
2018-10-2 11:13:01 显示全部楼层
tan 发表于 2018-10-2 10:11
我把我阅读的总结哈。
位拷贝,也就是说的赋值了一份父类相同的值,如果有空间申请的话,那么析构会出现两 ...

很抱歉,看着有点晕。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了