类复制.省略 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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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做出来…