拷贝构造函数——深拷贝与浅拷贝

Posted T丶jl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了拷贝构造函数——深拷贝与浅拷贝相关的知识,希望对你有一定的参考价值。

拷贝构造函数(一)——哲学三连:http://www.cnblogs.com/tenjl-exv/p/8017814.html

拷贝构造函数(二)——深拷贝与浅拷贝:http://www.cnblogs.com/tenjl-exv/p/8017909.html

拷贝构造函数(三)——重载赋值运算符:http://www.cnblogs.com/tenjl-exv/p/8017983.html

1. 默认拷贝构造函数

很多时候在我们都不知道拷贝构造函数的情况下,

传递对象给函数参数或者函数返回对象都能很好的进行,

这是因为编译器会给我们自动产生一个拷贝构造函数,

这就是“默认拷贝构造函数”。

这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值,

它一般具有以下形式:

 

1 Rect::Rect(const Rect& r)  
2 {  
3     width = r.width;  
4     height = r.height;  
5 }  

 

当然,以上代码不用我们编写,编译器会为我们自动生成。

但是如果认为这样就可以解决对象的复制问题,那就错了。

2. 浅拷贝

所谓浅拷贝,指的是在对象复制时,

只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。

大多情况下“浅拷贝”已经能很好地工作了,

但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。

让我们考虑如下一段代码:

 1 class Rect  
 2 {  
 3 public:  
 4     Rect()      // 构造函数,p指向堆中分配的一空间  
 5     {  
 6         p = new int(100);  
 7     }  
 8     ~Rect()     // 析构函数,释放动态分配的空间  
 9     {  
10         if(p != NULL)  
11         {  
12             delete p;  
13         }  
14     }  
15 private:  
16     int width;  
17     int height;  
18     int *p;     // 一指针成员  
19 };  
20   
21 int main()  
22 {  
23     Rect rect1;  
24     Rect rect2(rect1);   // 复制对象  
25     return 0;  
26 }  

在这段代码运行结束之前,会出现一个运行错误。

原因就在于在进行对象复制时,对于动态分配的内容没有进行正确的操作。

我们来分析一下:

在运行定义rect1对象后,

由于在构造函数中有一个动态分配的语句,因此执行后的内存情况大致如下:

 

 

在使用rect1复制rect2时,由于执行的是浅拷贝,

只是将成员的值进行赋值,这时 rect1.p = rect2.p,

也即这两个指针指向了堆里的同一个空间,如下图所示:

当然,这不是我们所期望的结果,

在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。

我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。

3. 深拷贝

在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,

而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:

 1 class Rect  
 2 {  
 3     public:  
 4         Rect()      // 构造函数,p指向堆中分配的一空间  
 5         {  
 6             p = new int(100);  
 7         }  
 8         Rect(const Rect& r)  
 9         {  
10             width = r.width;  
11             height = r.height;  
12             p = new int;        // 为新对象重新动态分配空间  
13             *p = *(r.p);  
14         }  
15         ~Rect()                  // 析构函数,释放动态分配的空间  
16         {  
17             if(p != NULL)  
18             {  
19                 delete p;  
20             }  
21         }  
22     private:  
23         int width;  
24         int height;  
25         int *p;     // 一指针成员  
26 };  

此时,在完成对象的复制后,内存的一个大致情况如下:

 

此时rect1的p和rect2的p各自指向一段内存空间,

但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

四. 拷贝构造函数的几个细节

1. 拷贝构造函数里能调用private成员变量吗?

这个问题是在网上见的,

其时从名字我们就知道拷贝构造函数其时就是一个特殊的构造函数,

操作的还是自己类的成员变量,所以不受private的限制。

2. 以下函数哪个是拷贝构造函数,为什么?

X::X(const X&);      
X::X(X);      
X::X(X&, int a=1);      
X::X(X&, int a=1, int b=2);  

对于一个类X, 如果一个构造函数的第一个参数是下列之一:

a) X&
b) const X&
c) volatile X&
d) const volatile X&

且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.

X::X(const X&);                 //是拷贝构造函数      
X::X(X&, int=1);                //是拷贝构造函数     
X::X(X&, int a=1, int b=2);     //当然也是拷贝构造函数  

3. 一个类中可以存在多于一个的拷贝构造函数吗?
类中可以存在超过一个拷贝构造函数。

class X {   
public:         
  X(const X&);      // const 的拷贝构造  
  X(X&);            // 非const的拷贝构造  
};  
 

注意,如果一个类中只存在一个参数为 X& 的拷贝构造函数,

那么就不能使用const X或volatile X的对象实行拷贝初始化.

class X {      
public:  
  X();      
  X(X&);  
};      
  
const X cx;      
X x = cx;    // error  

如果一个类中没有定义拷贝构造函数,

那么编译器会自动产生一个默认的拷贝构造函数。
这个默认的参数可能为 X::X(const X&)或 X::X(X&),

由编译器根据上下文决定选择哪一个。

 

以上是关于拷贝构造函数——深拷贝与浅拷贝的主要内容,如果未能解决你的问题,请参考以下文章

深拷贝与浅拷贝

深拷贝与浅拷贝

深拷贝与浅拷贝,还有一道比较好的面试题

引用传递与值传递______深拷贝与浅拷贝______构造函数与析构函数

Java 深拷贝与浅拷贝概念与代码实现

深拷贝与浅拷贝[重复]