文章目錄
- 一、持有資源的類定義移動構造函數的要點
- 1.普通內置類型與std::move
- 2.常見的容器與std::move
- 3.結構體:
- 4.智能指針與std::move
- 參考
一、持有資源的類定義移動構造函數的要點
1.普通內置類型與std::move
在C++中,std::move 主要用于對象的移動語義,對于大多數內置類型(如整數),移動語義實際上沒有意義。對于內置類型(如整數、浮點數等),移動與復制的成本是相同的,因為它們的大小是固定且已知的,且復制的成本非常低廉。因此,使用 std::move 對這些類型沒有實際的優化效果。
盡管如此,使用 std::move 來處理整數變量是完全合法的,但它不會帶來任何性能上的優勢。下面是一個簡單的例子:
#include <iostream>
#include <utility> // for std::movevoid printAndMove(int &&num) {std::cout << "Value: " << num << std::endl;
}int main() {int a = 42;printAndMove(std::move(a));// a 的值仍然是 42,因為對于內置類型沒有“移動”語義std::cout << "Value of a after move: " << a << std::endl;return 0;
}
總結:
- 對于像整數這樣的內置類型,使用 std::move 沒有實際的性能收益。
- 對于更復雜的類型(如 std::string、std::vector 等),std::move 可以避免昂貴的復制操作,通過轉移資源提高性能。
2.常見的容器與std::move
#include <iostream>
#include <string>
#include <utility> // for std::moveint main() {std::string original = "Hello, world!";std::string moved_to = std::move(original);std::cout << "Moved-to string: " << moved_to << std::endl;std::cout << "Original string after move: " << original << std::endl;return 0;
}
Program returned: 0
Program stdout
Moved-to string: Hello, world!
Original string after move:
在這段代碼中:
- 創建并初始化一個名為 original 的 std::string,內容為 “Hello, world!”。
- 使用 std::move 函數將 original 轉換為右值引用,從而允許內容被移動到 moved_to。
- 移動之后,moved_to 包含 “Hello, world!”,而 original 處于有效但未指定的狀態。通常來說,這意味著 original 變為空字符串。
3.結構體:
對于普通結構體,std::move 可以有效地將資源從一個對象轉移到另一個對象。這對于結構體內部包含動態分配的資源(如動態數組或其他需要管理內存的成員變量)時尤為重要。在這種情況下,通過使用 std::move,可以避免昂貴的復制操作,提高性能。
#include <iostream>
#include <vector>
#include <utility> // for std::movestruct MyStruct {std::vector<int> data;MyStruct(std::vector<int> d) : data(std::move(d)) {}// 移動構造函數MyStruct(MyStruct&& other) noexcept : data(std::move(other.data)) {std::cout << "Move constructor called" << std::endl;}// 移動賦值運算符MyStruct& operator=(MyStruct&& other) noexcept {if (this != &other) {data = std::move(other.data);std::cout << "Move assignment operator called" << std::endl;}return *this;}// 禁用復制構造函數和復制賦值運算符MyStruct(const MyStruct&) = delete;MyStruct& operator=(const MyStruct&) = delete;
};int main() {MyStruct s1(std::vector<int>{1, 2, 3, 4, 5});// 使用 std::move 將 s1 轉移到 s2MyStruct s2 = std::move(s1);std::cout << "s1 data size after move: " << s1.data.size() << std::endl;std::cout << "s2 data size after move: " << s2.data.size() << std::endl;MyStruct s3(std::vector<int>{6, 7, 8, 9, 10});// 使用 std::move 將 s3 轉移到 s2s2 = std::move(s3);std::cout << "s3 data size after move: " << s3.data.size() << std::endl;std::cout << "s2 data size after move assignment: " << s2.data.size() << std::endl;return 0;
}
Program returned: 0
Program stdout
Move constructor called
s1 data size after move: 0
s2 data size after move: 5
Move assignment operator called
s3 data size after move: 0
s2 data size after move assignment: 5
對于僅包含普通內置類型的結構體,std::move 雖然是合法的,但實際上沒有什么效果,因為內置類型的移動與復制是一樣的。內置類型(如 int、double 等)的復制開銷很低,并且移動這些類型不會帶來性能提升。
但是,如果你定義了移動構造函數和移動賦值運算符,可以確保你的結構體在更復雜的情況下(例如成員類型改變)也能正確處理移動語義。
#include <iostream>
#include <utility> // for std::movestruct MyStruct {int a;double b;// 默認構造函數MyStruct(int x, double y) : a(x), b(y) {}// 移動構造函數MyStruct(MyStruct&& other) noexcept : a(other.a), b(other.b) {std::cout << "Move constructor called" << std::endl;// 這里可以將其他對象的值重置,但通常沒有必要}// 移動賦值運算符MyStruct& operator=(MyStruct&& other) noexcept {if (this != &other) {a = other.a;b = other.b;std::cout << "Move assignment operator called" << std::endl;// 這里可以將其他對象的值重置,但通常沒有必要}return *this;}// 禁用復制構造函數和復制賦值運算符MyStruct(const MyStruct&) = delete;MyStruct& operator=(const MyStruct&) = delete;
};int main() {MyStruct s1(42, 3.14);// 使用 std::move 將 s1 轉移到 s2MyStruct s2 = std::move(s1);std::cout << "s1.a after move: " << s1.a << std::endl; // 值仍然存在,但不再關心std::cout << "s1.b after move: " << s1.b << std::endl; // 值仍然存在,但不再關心std::cout << "s2.a after move: " << s2.a << std::endl;std::cout << "s2.b after move: " << s2.b << std::endl;MyStruct s3(100, 2.71);// 使用 std::move 將 s3 轉移到 s2s2 = std::move(s3);std::cout << "s3.a after move: " << s3.a << std::endl; // 值仍然存在,但不再關心std::cout << "s3.b after move: " << s3.b << std::endl; // 值仍然存在,但不再關心std::cout << "s2.a after move assignment: " << s2.a << std::endl;std::cout << "s2.b after move assignment: " << s2.b << std::endl;return 0;
}
Move constructor called
s1.a after move: 42
s1.b after move: 3.14
s2.a after move: 42
s2.b after move: 3.14
Move assignment operator called
s3.a after move: 100
s3.b after move: 2.71
s2.a after move assignment: 100
s2.b after move assignment: 2.71
4.智能指針與std::move
在C++中,std::move 對于智能指針(如 std::unique_ptr 和 std::shared_ptr)非常有用,因為智能指針管理動態分配的資源,如內存。通過使用 std::move,可以將智能指針的所有權從一個對象轉移到另一個對象,而無需復制底層資源。這有助于避免資源泄漏并確保資源的唯一所有權。
#include <iostream>
#include <memory> // for std::unique_ptr and std::make_uniquevoid processUniquePtr(std::unique_ptr<int> ptr) {std::cout << "Processing unique pointer with value: " << *ptr << std::endl;
}int main() {std::unique_ptr<int> myPtr = std::make_unique<int>(42);// 使用 std::move 轉移 myPtr 的所有權到 processUniquePtrprocessUniquePtr(std::move(myPtr));// myPtr 此時不再擁有資源,應該為 nullptrif (myPtr == nullptr) {std::cout << "myPtr is now nullptr after move" << std::endl;}return 0;
}
在這個例子中:
- 創建了一個 std::unique_ptr 類型的智能指針 myPtr,并使用 std::make_unique 進行初始化,指向一個值為 42 的整數。
- 使用 std::move(myPtr) 將 myPtr 的所有權轉移給 processUniquePtr 函數。
- 在 processUniquePtr 函數中,打印出智能指針指向的值。
- 在所有權轉移后,myPtr 被設置為 nullptr,因為它不再擁有資源。
#include <iostream>
#include <memory> // for std::shared_ptr and std::make_sharedvoid processSharedPtr(std::shared_ptr<int> ptr) {std::cout << "Processing shared pointer with value: " << *ptr << std::endl;std::cout << "Shared pointer use count: " << ptr.use_count() << std::endl;
}int main() {std::shared_ptr<int> myPtr = std::make_shared<int>(42);std::cout << "Initial use count: " << myPtr.use_count() << std::endl;// 使用 std::move 轉移 myPtr 的所有權到 processSharedPtrprocessSharedPtr(std::move(myPtr));// myPtr 此時仍然是有效的,但 use_count 應該減少if (myPtr == nullptr) {std::cout << "myPtr is now nullptr after move" << std::endl;} else {std::cout << "myPtr is still valid after move, use count: " << myPtr.use_count() << std::endl;}return 0;
}
在這個例子中:
- 創建了一個 std::shared_ptr 類型的智能指針 myPtr,并使用 std::make_shared 進行初始化,指向一個值為 42 的整數。
- 打印初始的引用計數。
- 使用 std::move(myPtr) 將 myPtr 的所有權轉移給 processSharedPtr 函數。
- 在 processSharedPtr 函數中,打印出智能指針指向的值和當前的引用計數。
- 在所有權轉移后,myPtr 仍然有效,但引用計數應減少。
總結:
- std::move 對于智能指針(尤其是 std::unique_ptr)非常有用,可以轉移所有權而不是復制資源。
- 對于 std::shared_ptr,使用 std::move 可以減少引用計數操作的開銷,但共享資源的所有權仍然被多個智能指針共享。
參考
- 【C++11】自己封裝RAII類,有哪些坑點?帶你了解移動語義的真相
- move