cpp的深拷贝和浅拷贝

默认拷贝构造

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Test
{
public:
Test(int k,int v):key(k),value(v)
{

}

void show()
{
std::cout<<key<<":"<<value<<std::endl;

}
private:
int key;
int value;

};
int main(int argc, char *argv[])
{
Test a(10,25);

a.show();
Test b(a); //Test b=a; //效果一样

b.show();
}

结果如下:

在这个Test类中我并没有实现拷贝构造,但是编译器自动实现了一份拷贝构造,并且也重载了=操作符。但是这个默认的拷贝构造函数仅仅只是实现了一个浅拷贝,当类的成员中存在指针对象时这种默认拷贝构造就会出现未知的错误。下面是一份错误的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Test
{
public:
Test(int k,int v):key(k),value(v)
{
p=new int(100);
}
virtual ~Test()
{
if(p)
delete p;
}
void show()
{
std::cout<<key<<":"<<value<<std::endl;

}
private:
int key;
int value;
int *p;
};
int main(int argc, char *argv[])
{
Test a(10,25);
a.show();
Test b(a);
b.show();
}

结果如下:

Test新增了一个int型指针,构造函数里面分配内存空间,析构函数里面释放内存,咋一看没有问题,但是运行结束结束之前会出现一个错误,导致程序无法正常结束。那是因为这个默认拷贝构造在处理int指针时,只是单纯的将b的指针p指向了a的指针p指向的相同内存空间。也就是说这两个指针指向了同一个内存空间,当两个Test对象析构的时候就会释放两次这个内存,导致报错。

深拷贝

针对上述出现的错误,所以需要自己实现一份深拷贝构造函数。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
class Test
{
public:
Test(int k,int v):key(k),value(v)
{
p=new int(100);
}
Test(const Test& val)
{
key=val.key;
value=val.value;
p=new int(100);
*p=*(val.p);
}
virtual ~Test()
{
if(p)
delete p;
}
void show()
{
std::cout<<key<<":"<<value<<std::endl;

}
private:
int key;
int value;
int *p;
};

int main(int argc, char *argv[])
{
Test a(10,25);
a.show();
Test b(a);
b.show();
}

结果如下:

在上述的拷贝构造的构造中,为对象b的指针p重新分配了新的内存空间,然后把对象a的指针p的指向的内容赋值给b.p指向的内存空间。这样就实现了每个对象的指针指向独立的内存空间,并且内容一样。

注意事项

1、拷贝构造的参数传递

上述代码采用了常量引用方式(const &)。为什么不采用传值要采用传引用?第一,传值会需要拷贝a对象的副本,导致额外的开销,影响效率。第二,也是最严重的问题,会导致递归无限调用拷贝构造函数。const是为了在传引用的时候,参数既可以使用左值(变量),又可以使用右值(常量),而且可以保证参数不被修改。当没有const的时候,参数只能使用左值,无法使用右值。

2、避免默认的拷贝构造函数

在类的定义中定义一个私有的拷贝构造函数如下:

1
2
private:
Test(const Test&);

这样在不要拷贝的环境中,如果用户错误的调用了拷贝构造,会触发编译器错误。

3、析构函数

析构函数前面加一个virtual使其变为一个虚函数。这是利用了cpp的多态。假设Test是一个基类,后续的继承类又定义了新的指针成员变量,这个时候继承类就需要自己重写析构函数。如果没有Test类的析构函数没有定义成虚函数,那么在继承类释放的时候,不会调用自己的析构函数,而是调用父类的析构函数,这样就会导致内存泄漏。


cpp的深拷贝和浅拷贝
http://yoursite.com/2019/06/12/cpp的深拷贝和浅拷贝/
作者
还在输入
发布于
2019年6月12日
许可协议