why std::move
全網最詳細介紹 std::move (自認啦)
當我們的 project 選擇使用 c++ 實作時,常常是因為 c++ 的執行效率很好,若在硬體資源較不足的情況下, ex: 嵌入式系統, 任何可以被優化的部分都要被優化。而 std::move 也是效率優化的一部分,特別是用在物件的拷貝上。 std::move 是 C++ 標準庫中的一個函數模板,定義在 <utility> 頭文件中。它的主要作用是將一個對象的所有權從一個變量轉移到另一個變量,而不進行昂貴的深拷貝。這在優化性能,尤其是涉及大量數據時非常有用。
我們先來介紹何謂深拷貝、何謂淺拷貝? 後面講到 std::move 就更清楚了。 先預告,這篇內容較多。
深拷貝 VS 淺拷貝
先說結論:
深拷貝:獨立的資源副本,新舊對象之間完全獨立,適用於需要保證數據獨立性的場景。 淺拷貝:資源共享,新舊對象共享相同的資源,可能導致資源管理問題(如重複釋放)。
深拷貝
直接看 code
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
47
48
49
50
51
52
53
54
#include <iostream>
#include <cstring>
class DeepCopy
{
private:
int* data;
int len;
public:
DeepCopy(int len)
{
auto data = new int[len] {1,2,3,4,5};
std::cout << "address: " << data << std::endl;
this->data = data;
this->len = len;
}
void print()
{
std::cout << "in print function: " << std::endl;
std::cout <<"address: " << data << std::endl;
std::cout << "data " << std::endl;
for (size_t i = 0; i < len; i++)
{
std::cout << data[i] << " ";
}
std::cout << std::endl;
}
DeepCopy(const DeepCopy& other) {
len = other.len;
data = new int[other.len];
memcpy(data, other.data, sizeof(int) * other.len);
}
~DeepCopy()
{
delete[] data;
}
};
int main()
{
DeepCopy dc(5);
dc.print();
DeepCopy dc2 = dc;
dc2.print();
return 0;
}
上述可以直接看到兩個物件的 data 的 address 是不同的,因此不論 data 如何改變,皆不影響另一個物件內的 data 內容。
淺拷貝
這個例子會示範淺拷貝的問題,也就是資源重複釋放。
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
47
48
49
#include <iostream>
#include <cstring>
class ShallowCopy
{
private:
int* data;
int len;
public:
ShallowCopy(int len)
{
auto data = new int[len] {1, 2, 3, 4, 5};
std::cout << "address: " << data << std::endl;
std::cout << data << std::endl;
this->data = data;
this->len = len;
}
void print()
{
std::cout << "in print function: " << std::endl;
std::cout << "address: " << data << std::endl;
std::cout << "data " << std::endl;
if (data != nullptr)
{
for (size_t i = 0; i < len; i++)
{
std::cout << data[i] << " ";
}
}
std::cout << std::endl;
}
~ShallowCopy()
{
delete[] data;
}
};
int main()
{
ShallowCopy sc(5);
sc.print();
ShallowCopy sc2 = sc;
sc2.print();
return 0;
}
因兩個物件內部的 data 指向同一個位置,在物件超出 scope 要被回收時,call destructor 造成該位置記憶體被 delete 兩次。
1
2
3
4
~ShallowCopy()
{
delete[] data;
}
BTW, 一般物件沒有特別去寫拷貝構造函數, default 就是淺拷貝。這要特別注意。
std::move
std::move 終於出場了
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
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <cstring>
class ShallowCopy
{
private:
int* data;
int len;
public:
ShallowCopy(int len)
{
auto data = new int[len] {1, 2, 3, 4, 5};
std::cout << "address: " << data << std::endl;
std::cout << data << std::endl;
this->data = data;
this->len = len;
}
void print()
{
std::cout << "in print function: " << std::endl;
std::cout << "address: " << data << std::endl;
std::cout << "data " << std::endl;
if (data != nullptr)
{
for (size_t i = 0; i < len; i++)
{
std::cout << data[i] << " ";
}
}
std::cout << std::endl;
}
ShallowCopy(ShallowCopy&& other) noexcept : data(other.data), len(other.len) {
other.data = nullptr;
other.len = 0;
std::cout << "Moved" << std::endl;
}
~ShallowCopy()
{
delete[] data;
}
};
int main()
{
ShallowCopy sc(5);
sc.print();
ShallowCopy sc2 = std::move(sc);
sc2.print();
return 0;
}
資源重複釋放的問題消失了。
結論:
- 淺拷貝可能會導致資源重複釋放問題。
- 深拷貝避免了資源重複釋放問題,但可能會有性能瓶頸。
- 移動語義通過資源轉移避免了淺拷貝的資源重複釋放問題,同時避免了深拷貝的性能瓶頸。
使用淺拷貝 & 深拷貝 -> 必須實做拷貝構造函數 語法為:
1
MyClass(const MyClass& other) {...your code...}
若沒有特別實作 -> default 為淺拷貝 使用 std::move -> 必須實作移動構造函數 語法為:
1
MyClass(const MyClass&& other) {...your code...}
可自己複製上面的 code 執行看看,更有感覺。 好拉,終於講完了。



