C++11新特性学习笔记之移动构造函数
指针成员和浅拷贝
一般来说,如果一个类中有指针成员,则要小心拷贝成员函数的编写,因为如果不注意,则会造成程序的内存泄漏。如下所示的例子。
#include <iostream>
class HasPtrMem{
public:
HasPtrMem() : m_data(new int(0)){}
~HasPtrMem(){
if (m_data != nullptr)
{
delete m_data;
m_data = nullptr;
}
}
int *m_data;
};
int main(){
HasPtrMem a;
HasPtrMem b(a);
std::cout << a.m_data << std::endl;//0
std::cout << b.m_data << std::endl;//0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
我们申明了一个带有指针成员的HasPtrMem类,在main函数中声明了对象a,再用对象a去初始化b,由于类中没有显示声明拷贝构造函数,则按照C++语法会调用编译器隐式生成的拷贝构造函数。这样就会出现以下一个问题:
a和b的m_data和都指向同一块堆内存,因此在main作用域结束后,a和b的析构函数依次被调用,比如b.m_data所指向的内存先被释放后,a.m_data就无法再对其进行操作,从而导致内存泄漏。
上述拷贝构造方式就是浅拷贝(shallow copy):只对数据成员进行简单的赋值。
深拷贝
深拷贝(deep copy):针对存在指针数据成员的情况下,重新分配内存空间,不再是简单的指针赋值。如下所示。
#include <iostream>
class HasPtrMem{
public:
HasPtrMem() : m_data(new int(0)){}
HasPtrMem(HasPtrMem& h) : m_data(new int(*h.m_data)){} //拷贝构造函数,从堆中分配内存,用h.m_data初始化
~HasPtrMem(){
if (m_data != nullptr)
{
delete m_data;
m_data = nullptr;
}
}
int *m_data;
};
int main(){
HasPtrMem a;
HasPtrMem b(a);
std::cout << *a.m_data << std::endl;//0
std::cout << *b.m_data << std::endl;//0
} //正常析构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行结果:
0
0
上述结果就不会报错,新的拷贝构造函数从堆中分配心内存,将分配来的内存的指针交给m._data,通过这样的方法,就能避免产生悬挂指针(dangling pointer)。
移动构造函数
如果指针所指向非常大的内存数据的话,则拷贝构造的代价就非常昂贵,会极大地影响性能。C++11提供一种简洁解决方法:移动构造函数,即是在用原对象指针对新对象指针进行赋值后,将原对象成员指针置为空指针,使得其无法指向内存数据,从而保证在析构的时候不会产生内存泄漏。这样既不用分配新内存,也不会产生内存泄漏,从而很好地解决了上述问题。如下所示。
#include <iostream>
class HasPtrMem{
public:
HasPtrMem() : m_data(new int(0)){
std::cout << "Construct: " << ++n_cstr << std::endl;
}
HasPtrMem(const HasPtrMem &h) : m_data(new int(*h.m_data)){
std::cout << "Copy construct: " << ++n_cptr << std::endl;
}
HasPtrMem(HasPtrMem&& h) :m_data(h.m_data){//移动构造函数
h.m_data = nullptr; //将临时值的指针成员置空
std::cout << "Move construct: " << ++n_mvtr << std::endl;
}
~HasPtrMem(){
if (m_data != nullptr)
{
delete m_data;
m_data = nullptr;
}
std::cout << "Destruct: " << ++n_dstr << std::endl;
}
int *m_data;
static int n_cstr;
static int n_dstr;
static int n_cptr;
static int n_mvtr;
};
int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_cptr = 0;
int HasPtrMem::n_mvtr = 0;
int HasPtrMem::n_dstr = 0;
HasPtrMem GetTemp(){
HasPtrMem h;
std::cout << "Resource from " << __FUNCTION__ << ":" << std::hex << h.m_data << std::endl;
return h;
}
int main()
{
HasPtrMem a = GetTemp();
std::cout << "Resource from " << __FUNCTION__ << ":" << std::hex << a.m_data << std::endl;
system("pause");
return 0;
}
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
39
40
41
42
43
44
45
46
运行结果:
Construct: 1
Resource from GetTemp:000000E2E9B55D00
Move construct: 1
Destruct: 1
Resource from main:000000E2E9B55D00
Destruct: 2
1
2
3
4
5
6
GetTemp()函数返回了h的一个临时变量时,调用了移动构造函数,其中的h和main中的a的指针成员值是相同的,说明了h.m _data和a.m _data都指向了相同的堆内存地址.因此其指针在GetTemp函数返回时免于被析构的“命运”,成为main中a的变量。
总结
移动构造函数能解决占内存较大的对象的拷贝构造问题,对提高系统性能有很好的作用。
---------------------
作者:Roy-bin
来源:CSDN
原文:https://blog.csdn.net/gxb0505/article/details/53572761
版权声明:本文为博主原创文章,转载请附上博文链接!