对于默认的拷贝赋值操作符,在如下情况下不会表现出按位拷贝(bitwise copy:关于按位拷贝,实际就是不使用拷贝构造函数或者拷贝赋值操作符,这里的不使用是指编译器根本不会产生,而是采用按位拷贝对象数据的方式,若对象中含有指针,此时的指针只是地址级别的浅拷贝,可能会引起内存问题)
(位拷贝就是浅拷贝,值拷贝就是深拷贝)
a. 当类内带有一个含有拷贝赋值操作符的成员变量时。
b. 当类的基类含有拷贝赋值操作符时。
c. 当类声明了虚函数时,此时不能直接拷贝右端类对象的虚函数指针,因为右边可能是子类对象。
d. 当类继承自虚基类时(不管此虚基类有无拷贝赋值操作符)。
这时候浅拷贝就会出现问题
例如:
- class Point
- {
- public:
- Point(float x = 0.0, float y = 0.0);
- //没有虚函数
- protected:
- float _x, _y;
- };
- inline Point& Point::operator=(const Point& p)
- {
- _x = p._x;
- _y = p._y;
- return *this;
- }
- class Point3d : virtual public Point {
- public:
- Point3d(float x = 0.0, float y = 0.0, float z = 0.0)
- //...
- protected:
- float _z;
- };
- inline Point3d& Point3d::operator=(Point3d* const this, const Point3d& p)
- {
- //invoke base's operator=
- this->Point::operator=(p);//or can invoke like this: (*(Point*)this) = p;
- //memberwise copy the derived class members
- _z = p.z;
- return *this;
- }
- inline Vertex3d& Vertex3d::operator=(const Vertex3d& v)
- {
- this->Point::operator=(v);
- this->Point3d::operator=(v);//会调用:this->Point::operator=(v);
- this->Vertex::operator=(v);//也会调用:this->Point::operator=(v);
- }
之所以会出现重复调用是因为获取拷贝赋值操作符的地址是合法的(关于成员函数指针可以参考中,”指向成员函数的指针部分
- typedef Point3d& (Point3d::* pmfPoint3d)(const Point3d&);
- pmfPoint3d pmf = &Point3d::operator=;
- (p.*pmf)(x);
关于此点,C++标准中的说法是:“我们并没有规定那些代表虚基类的子对象是否应该被隐喻定义的(implicitly defined)拷贝赋值操作符指派(assign)内容一次以上。”
由此可见,拷贝赋值操作符在虚拟继承的情况下行为不佳。
因此一条建议就是:不要在任何虚基类中声明数据成员。
浅拷贝构造函数
看一段拷贝构造函数的代码
#include <iostream>
#include <cstring>
using namespace std;
class Array{
public :
Array(){
cout<<"Array()"<<endl;
}
Array(const Array &arr){ /// 拷贝构造函数
m_iCount = arr.m_iCount;
cout<<"Array &"<<endl;
}
virtual ~Array(){
cout<<"~Array()"<<endl;
}
void setCount(int _count){
m_iCount = _count;
}
int getCount(){
return m_iCount;
}
private :
int m_iCount;
};
int main(){
Array arr1;
arr1.setCount(5);
Array arr2(arr1);///浅拷贝
cout<<"arr2 m_iCount "<<arr2.getCount()<<endl;
return 0;
}
类Array实例化一个arr1的对象,并给数据成员m_iCount赋值为5。
接着类Array实例化一个对象arr2并将arr1的数据成员的值拷贝给arr2
运行代码,显然arr1和arr2的数据成员m_iCount的值都为5,系统给arr1,arr2分配了内存空间并使得arr1的值复制给了arr2
接下来再看一段代码:
#include <iostream>
#include <cstring>
using namespace std;
class Array{
public :
Array(int _count){
m_iCount = _count;
m_pArr = new int[m_iCount];
cout<<"Array()"<<endl;
}
Array(const Array &arr){ /// 拷贝构造函数
m_iCount = arr.m_iCount;
m_pArr = arr.m_pArr; ///两个指针指向同一块内存
cout<<"Array &"<<endl;
}
virtual ~Array(){
delete []m_pArr;
m_pArr = NULL;
cout<<"~Array()"<<endl;
}
void setCount(int _count){
m_iCount = _count;
}
int getCount(){
return m_iCount;
}
void printAddr(){
cout<<"m_pArr : "<<m_pArr<<endl;
}
private :
int m_iCount;
int *m_pArr;
};
int main(){
Array arr1(5);
Array arr2(arr1);
arr1.printAddr();
arr2.printAddr();
return 0;
}
我们在类中添加一个数据成员int型的指针m_pArr,实例化一个对象arr1并给数据成员m_iCount赋值为5,与此同时系统也需要给另一个数据成员m_pArr在堆区分配内存空间
接着实例化一个对象arr2并将arr1的值复制给arr2,系统调用拷贝构造函数。将arr1.m_iCount赋值给arr2.m_iCount,将arr1.m_pArr赋值给arr2.m_iArr。因为在拷贝构造函数中,系统并没有在堆区分配一个内存空间给arr2的数据成员m_iArr。所以这里两个对象的数据成员m_iArr显然都指向了同一块内存空间,那么会有什么问题呢?
当我们在调用析构函数,释放内存空间的时候,两个对象指向的那块内存空间就会被释放两次,这样程序会奔溃,导致出错。
这就是浅拷贝带来的危险
这里再引入深拷贝:
Array(const Array &arr){ /// 拷贝构造函数
m_iCount = arr.m_iCount;
m_pArr = new int[m_iCount];
for(int i=0;i<m_iCount;i++) m_pArr[i]=arr.m_pArr[i];
cout<<"Array &"<<endl;
}
当类中存在数据成员需要动态开辟内存空间的时候,需要使用深拷贝的方式
区别:
在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。