//调用了拷贝构造函数的话,对应的指针变量 的值++;
SharedPointer(const SharedPointer<T*>& obj) :Pointer<T>(NULL) //这里也应该调用父类拷贝构造函数?
{
assign(obj);
}
//赋值本质上是没有增加指针变量的值 因为clear了(换句话---拷贝构造函数大多数情况下是复制,而赋值函数是引用对象)
//引用知识别名,所以本质上没有增加
SharedPointer<T>& operator= (const SharedPointer<T*>& obj){
if(this!= &obj){
clear();
assign(obj);
}
return *this;
}
问题1 拷贝构造函数和赋值函数的区别:
浅拷贝只是简单地对变量进行赋值,而对指针变量进行简单赋值只是将地址赋给新对象的指针变量,这样两个对象的指针变量指向同一块内存空间。这样根据析构函数的析构顺序,新对象析构掉的时候将内存释放了,就对象析构的时候就会导致内存访问出错。因此,自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数(这段话介绍一下 为什么要深拷贝!)
拷贝构造就是:用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,网上找到一个例子:
在面对象程序设计中,对象间的相互拷贝和赋值是经常进行的操作。
如果对象在申明的同时马上进行的初始化操作,则称之为拷贝运算。例如:
class1 A("af"); class1 B=A;
此时其实际调用的是B(A)这样的浅拷贝操作。
如果对象在申明之后,在进行的赋值运算,我们称之为赋值运算。例如:
class1 A("af"); class1 B;
B=A;
这样就回答了 拷贝构造和赋值的区别啦!
问题2 以共享智能指针为例子:
可以看到 构造函数调用的话 计数+1,而赋值却不会,因为赋值会清空=左边对象的状态 在赋值 ;
问题3 关于operator=的一些使用注意点:
一般输入的参数 是以const 修饰的:
1.加const是因为:
①我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
②加上const,对于const的和非const的实参,函数就能接受;如果不加,就只能接受非const的实参。
用引用是因为:这样可以避免在函数调用时对实参的一次拷贝,提高了效率。
2.一般地,返回值是被赋值者的引用,即*this,原因是
①这样在函数返回时避免一次拷贝,提高了效率。
②这样可以实现连续赋值,即类似a=b=c这样。如果不是返回引用而是返回值类型,那么,执行a=b时,调用赋值运算符重载函数,在函数返回时,由于返回的是值类型,所以要对return后边的“东西”进行一次拷贝,得到一个未命名的副本(有些资料上称之为“匿名对象”),然后将这个副本返回,而这个副本是右值,所以,执行a=b后,得到的是一个右值,再执行=c就会出错。
(可以看到引用在避免初始化 + 避免调用运算符重载 有很好的效果)
3.赋值运算符重载函数只能是类的非静态的成员函数
C++规定,赋值运算符重载函数只能是类的非静态的成员函数,不能是静态成员函数,也不能是友元函数。之所以不是静态成员函数,是因为静态成员函数只能操作类的静态成员,不能操作非静态成员。如果我们将赋值运算符重载函数定义为静态成员函数,那么,该函数将无法操作类的非静态成员,这显然是不可行的。为了避免这样的二义性,C++强制规定,赋值运算符重载函数只能定义为类的成员函数。
4.赋值运算符重载函数不能被继承
相较于基类,派生类往往要添加一些自己的数据成员和成员函数,如果允许派生类继承基类的赋值运算符重载函数,那么,在派生类不提供自己的赋值运算符重载函数时,就只能调用基类的,但基类版本只能处理基类的数据成员,在这种情况下,派生类自己的数据成员怎么办?所以,C++规定,赋值运算符重载函数不能被继承。
5.赋值运算符重载函数要避免自赋值
对于赋值运算符重载函数,我们要避免自赋值情况(即自己给自己赋值)的发生,一般地,我们通过比较赋值者与被赋值者的地址是否相同来判断两者是否是同一对。
既然看到了拷贝构造,再继续复习复习:
1.什么时候会调用拷贝构造函数呢?
在C++中,下面三种对象需要调用拷贝构造函数(有时也称“复制构造函数”):
1) 一个对象作为函数参数,以值传递的方式传入函数体;
2) 一个对象作为函数返回值,以值传递的方式从函数返回;
3) 一个对象用于给另外一个对象进行初始化(常称为复制初始化);
参考博主的很多内容(尤其 是赋值这一块):
作者:Temple_Li
链接:https://www.jianshu.com/p/ad43edd636dd
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。