類復制.省略 class.copy.elision

class類

復制/移動省略class.copy.elision

類復制省略 (copy elision)

當滿足特定條件時,即使所選對象的構造函數和/或析構函數有副作用,實現也被允許省略從相同類型(忽略 cv 限定符)的源對象創建類對象。

在這種情況下,實現將省略的初始化的源和目標視為引用同一對象的兩種不同方式。如果所選構造函數的第一個參數是對對象類型的右值引用,則該對象的析構發生在目標對象本應被析構的時刻;否則,析構發生在未進行優化時兩個對象本應被析構的較晚時刻

[注 1]: 因為只有一個對象被析構而不是兩個,并且一個對象的創建被省略了,所以對于每個構造的對象仍然只有一個對象被析構。——尾注

這種對象創建的省略,稱為復制省略 (copy elision),在以下情況下是允許的(這些情況可以組合以消除多次復制):

  1. 在返回語句中 (return statement): 在具有類返回類型的函數中,當表達式是具有自動存儲期的非 volatile 對象 o(不是函數參數,也不是由處理程序的異常聲明引入的變量)的名稱時,可以通過直接將 o 構造到函數調用的結果對象中來省略結果對象的復制初始化。

  2. 在 throw 表達式中 (throw-expression): 在 throw 表達式中,當操作數是具有自動存儲期的非 volatile 對象 o(不是函數參數,也不是由處理程序的異常聲明引入的變量)的名稱,且該對象所屬的作用域不包含與 try 塊關聯的最內層復合語句(如果存在)時,可以通過直接將 o 構造到異常對象中來省略異常對象的復制初始化。

  3. 在協程中 (coroutine): 在協程中,如果程序的語義除了執行參數拷貝對象的構造函數和析構函數之外保持不變,則可以省略協程參數的拷貝,并將對該拷貝的引用替換為對相應參數的引用。

  4. 在異常處理器的異常聲明中:當處理程序的異常聲明聲明了一個對象 o 時,如果程序的語義除了執行異常聲明所聲明的對象的構造函數和析構函數之外保持不變,則可以通過將異常聲明視為異常對象的別名來省略 o的復制初始化。

    [注 2]: 不能從異常對象進行移動,因為它總是左值。——尾注

在要求常量表達式的上下文中計算表達式時,以及在常量初始化時,不允許進行復制省略

[注 3]: 如果同一表達式在另一個上下文中求值,仍有可能執行復制省略。——尾注

[示例 1]:

class Thing {
public:Thing();~Thing();Thing(const Thing&);
};
Thing f() {Thing t;return t; // 允許省略將 t 復制/移動到 f() 的結果對象
}
Thing t2 = f(); // 允許省略將 f() 的返回值復制/移動到 t2struct A {void* p;constexpr A() : p(this) {}
};
constexpr A g() {A loc;return loc; // 常量求值上下文: 不允許省略!
}
constexpr A a; // 正確,a.p 指向 a
constexpr A b = g(); // 錯誤: b.p 將是懸垂指針 ([expr.const])
void h() {A c = g(); // 正確, c.p 可以指向 c 或是懸垂的
}

此例中,省略標準可以消除將具有自動存儲期的對象 t 復制到函數調用 f() 的結果對象(即非局部對象 t2)中。實際上,t 的構造可以被視為直接初始化 t2,并且該對象的析構將發生在程序退出時。向 Thing 添加移動構造函數具有相同的效果,但被省略的是從具有自動存儲期的對象到 t2 的移動構造。——尾例

[示例 2]:

class Thing {
public:Thing();~Thing();Thing(Thing&&);
private:Thing(const Thing&); // 復制構造函數私有,強制使用移動(如果可能)
};
Thing f(bool b) {Thing t;if (b)throw t; // 正確, 使用 Thing(Thing&&) (或省略) 來拋出 treturn t; // 正確, 使用 Thing(Thing&&) (或省略) 來返回 t
}
Thing t2 = f(false); // 正確, 沒有額外的復制/移動操作, t2 由對 f 的調用構造struct Weird {Weird();Weird(Weird&); // 注意:非常量左值引用復制構造函數
};
Weird g(bool b) {static Weird w1; // 靜態存儲期Weird w2;        // 自動存儲期if (b)return w1; // 正確, 使用 Weird(Weird&) (w1 是左值)elsereturn w2; // 錯誤: 在此上下文中 w2 是 xvalue (將亡值),但 Weird 沒有接受右值的構造函數
}int& h(bool b, int i) {static int s;if (b)return s; // 正確,返回靜態變量的左值引用elsereturn i; // 錯誤: i 是自動變量,在此上下文中是 xvalue,但函數返回左值引用
}decltype(auto) h2(Thing t) {return t; // 正確, t 是 xvalue, h2 的返回類型推導為 Thing (值類型)
}
decltype(auto) h3(Thing t) {return (t); // 正確, (t) 是 xvalue, h3 的返回類型推導為 Thing&& (右值引用)
}

——尾例

[示例 3]:

template <class T> void g(const T&);
template <class T> void f() {T x; // 外層作用域對象try {T y; // 內層作用域對象try {g(x);} catch (...) {if (/*...*/)throw x; // 不會移動 (x 屬于包含 try 塊的作用域,不符合 1.2 省略條件)throw y;     // 移動 (y 屬于不包含 try 塊的內層作用域,符合 1.2 省略條件。若省略則直接構造異常對象)}g(y);} catch (...) {g(x);g(y); // 錯誤: y 不在作用域內}
}

——尾例

核心總結:

此標準條款定義了 C++ 中的復制/移動省略 (Copy/Move Elision) 規則,這是編譯器為了優化性能而避免不必要的對象復制或移動的關鍵機制。

  1. 本質與效果:
    • 允許編譯器在特定條件下,完全省略從一個對象(源)創建另一個同類型對象(目標)的操作。
    • 被省略后,源和目標被視為同一個對象的兩種引用方式
    • 析構時機:若使用移動構造函數,則在目標對象該析構時析構;若使用復制構造函數,則在源和目標原該析構的較晚時刻析構。最終效果是只構造和析構了一個對象。
  2. 允許省略的場景 (可組合使用):
    • 命名返回值優化 (NRVO): 函數返回局部非 volatile 自動存儲期對象(非參數/異常聲明變量)的名稱時 (return local_var;),可省略將 local_var 復制/移動到函數返回值的操作,直接在返回值位置構造。
    • Throw 表達式優化: 拋出局部非 volatile 自動存儲期對象(非參數/異常聲明變量,且其作用域不包含最內層 try 塊)的名稱時,可省略將其復制/移動到異常對象的操作,直接在異常對象位置構造。
    • 協程參數省略: 在協程中,可省略對協程參數的拷貝。
    • 異常處理器別名:catch 塊的異常聲明中 (catch (Type obj)),可省略對異常對象的拷貝,直接將 obj 視為異常對象的別名。
  3. 禁止省略的場景:
    • 常量表達式求值: 在要求常量表達式的上下文中(如 constexpr 變量初始化、constexpr 函數內的 return)。
    • 常量初始化 (靜態初始化): 在靜態存儲期對象的常量初始化過程中。
  4. 關鍵點與影響:
    • 性能提升: 省略操作避免了潛在的昂貴復制/移動構造函數和析構函數調用,顯著提升性能。
    • 副作用容忍: 即使被省略的構造函數或析構函數有可觀測的副作用(如打印日志),編譯器仍被允許進行省略(這是 as-if 規則的例外)。
    • 移動構造的特殊性: 條款明確說明了當省略涉及移動構造函數時析構發生的時機。
    • 標準要求 (C++17起): 對于 NRVO (場景 1) 和純右值初始化,滿足條件時編譯器必須進行省略(稱為“強制省略”或“保證的復制省略”)。其他場景(如 throw 優化)是允許但不強制的。
    • 作用域與生存期: 示例 2 和 3 展示了對象的作用域(特別是相對于 try 塊的位置)如何影響省略的可行性,以及在異常處理中對象生存期的微妙問題。

簡而言之: 此條款賦予編譯器權力,在特定且定義明確的場景下(尤其是函數返回局部對象和拋出局部對象時),可以完全繞過復制或移動構造函數,直接“復用”源對象作為目標對象,從而生成更高效的代碼。理解這些規則對于編寫高性能 C++ 代碼和避免不必要的 std::move(如之前文章所述)至關重要。







原文翻譯

11.9 Initialization

11.9 初始化類.init

11.9.6 Copy/move elision

11.9.6 復制/移動省略[class.copy.elision]??[類復制.省略]

When certain criteria are met, an implementation is allowed to omit the creation of a class object from a source object of the same type (ignoring cv-qualification), even if the selected constructor and/or the destructor for the object have side effects.

當滿足某些條件時,實現為 允許省略 Class Object 的創建 相同類型的源對象(忽略 cv 限定), 即使所選構造函數和/或 對象的析構函數具有 副作用 。

In such cases, the implementation treats the source and target of the omitted initialization as simply two different ways of referring to the same object.

在這種情況下,實現將省略的初始化的 source 和 target 視為引用同一對象的兩種不同方式 。

If the first parameter of the selected constructor is an rvalue reference to the object’s type, the destruction of that object occurs when the target would have been destroyed; otherwise, the destruction occurs at the later of the times when the two objects would have been destroyed without the optimization.

如果所選構造函數的第一個參數是對象類型的右值引用,則當目標已銷毀時,將銷毀該對象;否則,銷毀發生在沒有優化的情況下銷毀兩個對象的時間 。

[Note?1:

Because only one object is destroyed instead of two, and the creation of one object is omitted, there is still one object destroyed for each one constructed.

—?end note]

[ 注?1

因為只有一個對象被銷毀而不是兩個,并且省略了一個對象的創建,所以每個構建的對象仍然有一個對象被銷毀 。

This elision of object creation, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

這種對象創建的省略稱為 復制省略 / 允許在 以下情況(可能會與 消除多個拷貝):

  • in a return statement ([stmt.return]) in a function with a class return type, when the expression is the name of a non-volatile object o with automatic storage duration (other than a function parameter or a variable introduced by the exception-declaration of a handler ([except.handle])), the copy-initialization of the result object can be omitted by constructing o directly into the function call’s result object;

    在具有類 return 類型的函數的 return 語句 ([stmt.return]) 中,當表達式是具有自動存儲持續時間的非易失性對象的名稱 o 時(函數參數或由 a 的異常聲明引入的變量除外) handler ([除外。handle])),可以通過將 o 直接構造到函數調用的 result 對象中來省略 result 對象的復制初始化;

  • in a throw-expression ([expr.throw]), when the operand is the name of a non-volatile object o with automatic storage duration (other than a function parameter or a variable introduced by the exception-declaration of a handler) that belongs to a scope that does not contain the innermost enclosing compound-statement associated with a try-block (if there is one), the copy-initialization of the exception object can be omitted by constructing o directly into the exception object;

    throw 表達式 ([expr.throw]),當作數是具有自動存儲持續時間的非易失性對象 o 的名稱(函數參數或處理程序的異常聲明引入的變量除外)時,該對象屬于不包含最內層封閉復合語句的作用域 與 try 塊 (如果有)相關聯,可以通過將 o 直接構造到 Exception 對象中來省略 Exception 對象的復制初始化;

  • in a coroutine, a copy of a coroutine parameter can be omitted and references to that copy replaced with references to the corresponding parameter if the meaning of the program will be unchanged except for the execution of a constructor and destructor for the parameter copy object;

    在協程中,如果程序的含義保持不變,則除了執行參數 copy 對象的構造函數和析構函數外,可以省略協程參數的副本,并將對該副本的引用替換為對相應參數的引用;

  • when the exception-declaration of a handler ([except.handle]) declares an object o, the copy-initialization of o can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.

    當處理程序 ([except.handle]) 的異常聲明聲明對象 o 時,如果程序的含義保持不變,則可以通過將異常聲明視為異常對象的別名來省略 o 的復制初始化,但異常聲明聲明的對象除外。

    [Note?2:

    There cannot be a move from the exception object because it is always an lvalue.

    —?end note]

    [ 注?2

    不能從異常對象移動,因為它始終是左值 。

[Note?3:

It is possible that copy elision is performed if the same expression is evaluated in another context.

—?end note]

[ 注?3

如果在另一個上下文中計算相同的表達式,則可能會執行復制省略 。

[Example?1:

class Thing {
public:Thing();~Thing();Thing(const Thing&);
};
Thing f() {Thing t;return t; // 允許省略將 t 復制/移動到 f() 的結果對象
}
Thing t2 = f(); // 允許省略將 f() 的返回值復制/移動到 t2struct A {void* p;constexpr A() : p(this) {}
};
constexpr A g() {A loc;return loc; // 常量求值上下文: 不允許省略!
}
constexpr A a; 		    // well-formed, a.p points to a ;正確,a.p 指向 a
constexpr A b = g(); 	// error: b.p would be dangling ([expr.const]);錯誤: b.p 將是懸垂指針 ([expr.const])
void h() {A c = g(); 			// well-formed, c.p can point to c or be dangling;正確, c.p 可以指向 c 或是懸垂的
}

—?end example]

這里的省略標準可以消除將具有自動存儲持續時間的對象 t 復制到函數調用 f() 的結果對象中,即非本地對象 t2。

實際上,t 的構造可以看作是直接初始化 t2,并且該對象的銷毀將在程序退出時發生 。

向 Thing 添加移動構造函數具有相同的效果,但省略了從具有自動存儲持續時間的對象到 t2 的移動構造 。

[Example?2:

class Thing {
public:Thing();~Thing();Thing(Thing&&);
private:Thing(const Thing&); // 復制構造函數私有,強制使用移動(如果可能)
};
Thing f(bool b) {Thing t;if (b)throw t; //OK, Thing(Thing&&) used (or elided) to throw t; 正確, 使用 Thing(Thing&&) (或省略) 來拋出 treturn t; // OK, Thing(Thing&&) used (or elided) to return t;正確, 使用 Thing(Thing&&) (或省略) 來返回 t
}
Thing t2 = f(false); // OK, no extra copy/move performed, t2 constructed by call to f
;正確, 沒有額外的復制/移動操作, t2 由對 f 的調用構造struct Weird {Weird();Weird(Weird&); // 注意:非常量左值引用復制構造函數
};
Weird g(bool b) {static Weird w1; // 靜態存儲期Weird w2;        // 自動存儲期if (b)return w1; // OK, uses Weird(Weird&);正確, 使用 Weird(Weird&) (w1 是左值)elsereturn w2; // error: w2 in this context is an xvalue;錯誤: 在此上下文中 w2 是 xvalue (將亡值),但 Weird 沒有接受右值的構造函數
}int& h(bool b, int i) {static int s;if (b)return s; // OK;正確,返回靜態變量的左值引用elsereturn i; // error: i is an xvalue;錯誤: i 是自動變量,在此上下文中是 xvalue,但函數返回左值引用
}decltype(auto) h2(Thing t) {return t; // OK, t is an xvalue and h2's return type is Thing;正確, t 是 xvalue, h2 的返回類型推導為 Thing (值類型)
}
decltype(auto) h3(Thing t) {return (t); // OK, (t) is an xvalue and h3's return type is Thing&&;正確, (t) 是 xvalue, h3 的返回類型推導為 Thing&& (右值引用)
}

—?end example]

[Example?3:

template <class T> void g(const T&);
template <class T> void f() {T x; // 外層作用域對象try {T y; // 內層作用域對象try {g(x);} catch (...) {if (/*...*/)throw x; //does not move; 不會移動 (x 屬于包含 try 塊的作用域,不符合 1.2 省略條件)throw y;     // moves;移動 (y 屬于不包含 try 塊的內層作用域,符合 1.2 省略條件。若省略則直接構造異常對象)}g(y);} catch (...) {g(x);g(y); // error: y is not in scope;錯誤: y 不在作用域內}
}

—?end example]

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

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

相關文章

goreplay

1.github地址 https://github.com/buger/goreplay 2.簡單介紹 GoReplay 是一個開源的網絡監控工具&#xff0c;可以記錄用戶的實時流量并將其用于鏡像、負載測試、監控和詳細分析。 3.出現背景 隨著應用程序的增長&#xff0c;測試它所需的工作量也會呈指數級增長。GoRepl…

TensorZero:開源 LLM 應用優化與可觀測性平臺

TensorZero 是一個開源的 LLM&#xff08;大語言模型&#xff09;應用全鏈路優化平臺&#xff0c;聚焦于“數據—評估—優化—實驗”自動化閉環&#xff0c;極大提升 LLM 產品的可觀測性、可優化性和可擴展性。無論你是 GPT 應用開發者&#xff0c;還是需要管理和提升 LLM 服務…

postgreSql數據遷移到openGauss的方案

從postgresql 導出sql 腳本 工具-備份 選擇格式為Plain 得到腳本用vscode 打開并編輯 首先使用查找替換功能 語法適配修改?&#xff1a; 替換不支持的參數如lock_timeout為lockwait_timeout 移除row_security等openGauss不支持的配置 檢查并修改物化視圖的刷新語法 …

網絡爬蟲學習心得

一、引言? 在大數據時代&#xff0c;數據成為了驅動決策、洞察趨勢的核心資源。出于對數據分析的濃厚興趣&#xff0c;以及希望能更高效獲取網絡信息的目的&#xff0c;我踏上了網絡爬蟲的學習之旅。通過這段時間的學習&#xff0c;我不僅掌握了從網頁中提取數據的技術&#…

計算機視覺與深度學習 | 基于Matlab的低照度圖像增強算法原理,公式及實現

基于Matlab的低照度圖像增強是一個重要的圖像處理領域。這里我們重點介紹一種經典且效果較好的算法:多尺度Retinex算法(Multi-Scale Retinex with Color Restoration, MSRCR),包括其原理、公式及Matlab實現。 一、核心原理:Retinex理論 Retinex理論由Edwin Land提出,其…

【Linux跬步積累】—— 網絡編程套接字(二)

&#x1f30f;博客主頁&#xff1a;PH_modest的博客主頁 &#x1f6a9;當前專欄&#xff1a;Linux跬步積累 &#x1f48c;其他專欄&#xff1a; &#x1f534; 每日一題 &#x1f7e1; C跬步積累 &#x1f7e2; C語言跬步積累 &#x1f308;座右銘&#xff1a;廣積糧&#xff0…

JavaScript基礎-API 和 Web API

在學習JavaScript的過程中&#xff0c;理解API&#xff08;應用程序接口&#xff09;和Web API的概念及其應用是非常重要的。這些工具極大地擴展了JavaScript的功能&#xff0c;使得開發者能夠創建出功能豐富、交互性強的Web應用程序。本文將深入探討JavaScript中的API與Web AP…

pikachu靶場通關筆記24 SQL注入07-http header注入

目錄 一、SQL注入 二、http header注入 1、User - Agent 頭注入 2、Referer 頭注入 3、Cookie 頭注入 4、Host 頭注入 三、extractvalue函數 四、源碼分析 1、代碼審計 2、滲透思路 五、滲透實戰 1、滲透探測 2、獲取數據庫名database 3、獲取表名table 4、獲取列…

LabVIEW振動時效處理系統

LabVIEW 開發大功率振動時效處理系統&#xff0c;實現工件殘余應力檢測與消除。聚焦工業場景中金屬加工件的應力處理需求&#xff0c;展現 LabVIEW 在跨硬件集成、實時數據處理及復雜流程控制中的技術優勢。 ? 應用場景 針對航空航天、軌道交通、重型機械等領域中鋼性焊接件…

數據定義以及數據類型

toc 數據定義以及數據類型 1. 數據創建 數據庫創建除了指定數據庫名字&#xff0c;還可以選擇指定數據庫字符集類型以及校對規則&#xff0c;mysql中utf8mb3就是utf8。 -- 使用指令創建數據庫 CREATE DATABASE hsp_db01; -- 刪除數據庫指令 DROP DATABASE hsp_db01 -- 創建…

中國汽車啟動電池市場深度剖析:現狀、趨勢與展望

一、市場規模與增長前景? QYResearch 調研團隊發布的市場報告顯示&#xff0c;中國汽車啟動電池市場展現出強勁的增長勢頭。預計到 2031 年&#xff0c;市場規模將攀升至 74.6 億美元&#xff0c;在未來幾年內&#xff0c;年復合增長率&#xff08;CAGR&#xff09;將穩定保持…

通過RedisCacheManager自定義緩存序列化(適用通過注解緩存數據)

1.Redis 注解默認序列化機制 1.Spring Boot整合Redis組件提供的緩存自動配置類RedisCacheConfiguration&#xff08;org.springframework.boot.autoconfigure.cache&#xff09;, 其內部是通過Redis連接工廠RedisConnectionFactory定義了一個緩存管理器RedisCacheManager&am…

jupyter中的checkpoints為空/打不開解決辦法

jupyter中的checkpoints為空/打不開不要以為你是代碼有問題或者服務器有問題了&#xff0c;浪費我好幾天時間&#xff0c;我說怎么電腦上跑的好好的服務器上模型不見了 新建文件check 然后把checkpoints里的東西全部移動到check文件中就能看見了 checkpoints是Notebook的關鍵…

基于 Spring AI 的 MCP 客戶端/服務端實現

模型上下文協議&#xff08;MCP&#xff09;由Anthropic開源的開放協議&#xff0c;為AI模型與外部數據/工具提供了“標準化橋梁”&#xff0c;通過統一的接口規范&#xff0c;使模型能夠動態調用本地文件、數據庫、API等資源&#xff0c;實現“上下文感知”的智能交互。MCP的核…

python學習打卡day50

DAY 50 預訓練模型CBAM模塊 知識點回顧&#xff1a; resnet結構解析CBAM放置位置的思考針對預訓練模型的訓練策略 差異化學習率三階段微調 ps&#xff1a;今日的代碼訓練時長較長&#xff0c;3080ti大概需要40min的訓練時長 作業&#xff1a; 好好理解下resnet18的模型結構嘗試…

54、錯誤處理-【源碼流程】異常處理流程

54、錯誤處理-【源碼流程】異常處理流程 #### 異常處理流程概述 1. **執行目標方法**&#xff1a; - 程序執行目標方法&#xff0c;期間若發生異常&#xff0c;會被捕獲并記錄&#xff0c;標志當前請求結束。 - 將異常信息賦值給 dispatchException 變量。 2. **進入視圖解析…

使用 VSCode 開發 FastAPI 項目(1)

一、引言 FastAPI 是一款現代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于構建 API&#xff0c;使用 Python 3.7 及更高版本。它基于標準 Python 類型提示&#xff0c;具有自動生成文檔等出色功能。而 VSCode 憑借其輕量、強大的特性&#xff0c;為開發者…

Bash 腳本中的特殊變量

在 Bash 腳本和命令行中&#xff0c;?特殊變量?&#xff08;Special Variables&#xff09;主要用于獲取腳本或命令的上下文信息&#xff0c;如參數、進程狀態、返回值等。以下是常見的特殊變量及其典型應用場景&#xff1a; ?1. 腳本參數處理? $0、$1、$2 ... $9、${10}.…

免部署的數字人 API 調用教程:基于 wav2lip模型訓練的開放API,附 PHP 代碼示例

前言 去年我開始研究數字人模型算法&#xff0c;測試了市面上幾乎所有開源數字人模型&#xff0c;過程中踩了不少坑。最大的痛點就是訓練太燒顯卡了&#xff0c;光租顯卡的費用就花了我6個月的薪資&#xff0c;每次看到賬單都心疼。不過現在終于把基于wav2lip的數字人API做出來…