C++11新特性學習筆記之移動構造函數
指針成員和淺拷貝
通常來講,若是一個類中有指針成員,則要當心拷貝成員函數的編寫,由於若是不注意,則會形成程序的內存泄漏。以下所示的例子。ios
#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就沒法再對其進行操做,從而致使內存泄漏。.net
上述拷貝構造方式就是淺拷貝(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;
};blog
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: 1Resource from GetTemp:000000E2E9B55D00Move construct: 1Destruct: 1Resource from main:000000E2E9B55D00Destruct: 2123456GetTemp()函數返回了h的一個臨時變量時,調用了移動構造函數,其中的h和main中的a的指針成員值是相同的,說明了h.m _data和a.m _data都指向了相同的堆內存地址.所以其指針在GetTemp函數返回時免於被析構的「命運」,成爲main中a的變量。