【C++11】自己封裝RAII類,有哪些坑點?帶你了解移動語義的真相

文章目錄

  • 一、持有資源的類定義移動構造函數的要點
    • 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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/39429.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39429.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39429.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Wireshark - tshark支持iptables提供數據包

tshark現在的數據包獲取方式有兩種&#xff0c;分別是讀文件、網口監聽&#xff08;af-packet原始套接字&#xff09;。兩種方式在包獲取上&#xff0c;都是通過讀文件的形式&#xff1b;存在文件io操作&#xff0c;在專門處理大流量的情境下&#xff0c; 我們復用wireshark去做…

Windows編程上

Windows編程[上] 一、Windows API1.控制臺大小設置1.1 GetStdHandle1.2 SetConsoleWindowInfo1.3 SetConsoleScreenBufferSize1.4 SetConsoleTitle1.5 封裝為Innks 2.控制臺字體設置以及光標調整2.1 GetConsoleCursorInfo2.2 SetConsoleCursorPosition2.3 GetCurrentConsoleFon…

python如何輸出list

直接輸出list_a中的元素三種方法&#xff1a; list_a [1,2,3,313,1] 第一種 for i in range(len(list_a)):print(list_a[i]) 1 2 3 313 1 第二種 for i in list_a:print(i) 1 2 3 313 1 第三種&#xff0c;使用enumerate輸出list_a方法&#xff1a; for i&#xff0c;j in enum…

Redis的使用(二)redis的命令總結

1.概述 這一小節&#xff0c;我們主要來研究一下redis的五大類型的基本使用&#xff0c;數據類型如下&#xff1a; redis我們接下來看一看這八種類型的基本使用。我們可以在redis的官網查詢這些命令:Commands | Docs,同時我們也可以用help 數據類型查看命令的幫助文檔。 2. 常…

數據結構 - C/C++ - 串

字符處理 C 特性 C語言中字符串存儲在字符數組中&#xff0c;以空字符\0結束。 字符串常量&#xff0c;const char* str "Hello"&#xff0c;存儲在只讀的數據段中。 布局 字符串在內存中是字符連續存儲的集合&#xff0c;最后一個字符為空字符(ASCII值為0)&…

opencascade AIS_InteractiveContext源碼學習7 debug visualization

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允許您在一個或多個視圖器中管理交互對象的圖形行為和選擇。類方法使這一操作非常透明。需要記住的是&#xff0c;對于已經被交互上下文識別的交互對象&#xff0c;必須使用上下文方法進行…

【問題已解決】Vue管理后臺,點擊登錄按鈕,會發起兩次網絡請求(竟然是vscode Compile Hero編譯插件導致的)

問題 VueElement UI 做的管理后臺&#xff0c;點擊登錄按鈕&#xff0c;發現 接口會連續掉兩次&#xff0c;發起兩次網絡請求&#xff0c;但其他接口都是正常調用的&#xff0c;沒有這個問題&#xff0c;并且登錄按鈕也加了loading&#xff0c;防止重復點擊&#xff0c;于是開…

搜索引擎常用語法

引號 (" "): 用雙引號將詞組括起來&#xff0c;搜索引擎將返回包含完全相同短語的結果。 示例&#xff1a;"人工智能發展趨勢" 減號 (-): 在關鍵詞前加上減號可以排除包含特定詞語的結果。 示例&#xff1a;人工智能 -機器學習&#xff08;排除包含 “機器…

樸素貝葉斯解密:sklearn中的分類器工作原理

&#x1f4da; 樸素貝葉斯解密&#xff1a;sklearn中的分類器工作原理 在機器學習領域&#xff0c;樸素貝葉斯分類器因其簡單、高效而廣受歡迎。特別是在處理大量特征數據時&#xff0c;樸素貝葉斯表現出了卓越的性能。scikit-learn&#xff08;簡稱sklearn&#xff09;是Pyth…

JavaMySQL 學習(基礎)

目錄 Java CMD Java發展 計算機存儲規則 Java學習 switch新用法&#xff08;可以當做if來使用&#xff09; 數組定義 隨機數 Java內存分配 MySQL MySQL概述 啟動和停止 客戶端連接 數據模型 關系型數據庫 SQL SQL通用語法 SQL分類 DDL--數據定義語言 數據庫…

瀏覽器開發者工具輔助爬蟲開發

文章目錄 瀏覽器開發者工具輔助爬蟲開發打開開發者工具使用Network面板分析請求數據示例步驟&#xff1a; 使用Elements面板查看和修改DOM結構示例步驟&#xff1a; 使用Console面板調試JavaScript代碼示例步驟&#xff1a;示例代碼&#xff1a;1. 輸出日志信息2. 輸出對象信息…

Vue 與 React 區別

Vue.js和React是現代Web開發中兩種非常流行的前端框架&#xff0c;兩者在**核心概念、組件以及生態系統擴展性**等方面存在區別。具體分析如下&#xff1a; 1. **核心概念** - **Vue**&#xff1a;Vue是一個漸進式JavaScript框架&#xff0c;它致力于視圖層&#xff0c;易于上手…

左值右值, 左值引用右值引用,完美轉發

一. 左值和右值 左值: 可以取地址的對象 右值: 不可以取地址的對象 double x1.0, y 2.0; 1; // 字面量, 不可取地址, 是右值 x y; // 表達式返回值, 不可取地址, 是右值 max(x, y); // 傳值返回函數的返回值 (非引用返回)總結就是: 根據是否可以取地址來區分是左值還…

線程池666666

1. 作用 線程池內部維護了多個工作線程&#xff0c;每個工作線程都會去任務隊列中拿取任務并執行&#xff0c;當執行完一個任務后不是馬上銷毀&#xff0c;而是繼續保留執行其它任務。顯然&#xff0c;線程池提高了多線程的復用率&#xff0c;減少了創建和銷毀線程的時間。 2…

git修改已提交的commit注釋

在Git中修改已經提交的commit注釋通常有以下幾種情況和相應的方法&#xff1a; 1. 修改最后一次提交的注釋&#xff08;快速修正&#xff09; 如果你想要修改的是最后一次提交的注釋&#xff0c;可以使用 --amend 選項&#xff1a; git commit --amend這個命令會將你的暫存區…

基于深度學習的光度檢測

基于深度學習的光度檢測&#xff08;Photometric Detection&#xff09;涉及從圖像中檢測和分析光照信息&#xff0c;用于多種應用&#xff0c;如場景理解、照明調節、增強現實&#xff08;AR&#xff09;、圖像增強等。以下是關于這一領域的系統介紹&#xff1a; 1. 任務和目…

JAVA基礎教程DAY1-類與方法及形參實參

首先經過C語言的學習&#xff0c;我們已經學會了基本的編程方法&#xff0c;我們知道C語言是面向過程的編程語言&#xff0c;而JAVA是面向對象的編程語言&#xff0c;所以接下來我們通過對比和舉例來進行JAVA語言的學習 首先我們來講類的概念 類&#xff1a;類是一個模板&…

Ubuntu開通5005端口 記錄

Ubuntu版本&#xff1a;20.04 使用systemctl status firewalld查看防火墻狀態&#xff0c;報錯Unit firewalld.service could not be found 報錯的原因是沒有安裝firewall&#xff0c;安裝命令為sudo apt install firewalld&#xff0c;然后進行安裝 安裝完成后輸入systemctl…

vscode jupyter選擇Python環境時找不到我安裝的Python

在一些情況下&#xff0c;我們需要自己安裝一個Python&#xff0c;在選擇內核是可能找不到指定的Python版本&#xff0c; 再次打開內核選擇頁面就能看到Python環境了 注意先到指定環境下安裝依賴包&#xff1a; ./python3 pip install ipykernel notebook jupyter

人工智能-NLP簡單知識匯總01

人工智能-NLP簡單知識匯總01 1.1自然語言處理的基本概念 自然語言處理難點&#xff1a; 語音歧義句子切分歧義詞義歧義結構歧義代指歧義省略歧義語用歧義 總而言之&#xff1a;&#xff01;&#xff01;語言無處不歧義 1.2自然語言處理的基本范式 1.2.1基于規則的方法 通…