在C++中禁用复制省略
Disable copy elision in C++
免责声明:研究的目标是如何禁用复制省略和返回值优化提供的代码部分。如果想提到XY问题之类的东西,请避免回答。这个问题具有严格的技术和研究性质,并以这种方式强烈提出
在 C++14 中引入了复制省略和返回值优化。如果某个对象在一个表达式中被破坏并复制构造,例如复制赋值或逐值从函数返回即时值,则省略复制构造函数。
以下推理应用于复制构造函数,但可以对移动构造函数执行类似的推理,因此不再考虑。
有一些部分解决方案可用于禁用自定义代码的复制省略:
1) 依赖于编译器的选项。对于 GCC,有基于__attribule__
或#pragma GCC
结构的解决方案,例如 https://stackoverflow.com/a/33475393/7878274 .但由于它依赖于编译器,因此没有遇到问题。
2)强制禁用复制构造函数,如Clazz(const Clazz&) = delete
。或者将复制构造函数声明为explicit
以防止它使用。这样的解决方案没有满足任务,因为它改变了复制语义并强制引入自定义名称函数,如Class::copy(const Clazz&)
。
3)使用中间类型,如这里描述 https://stackoverflow.com/a/16238053/7878274。由于此解决方案强制引入新的后代类型,因此没有遇到问题。
经过一些研究,发现恢复暂时价值可以解决问题。如果将源类重新解释为对此类的单元素数组的引用并提取第一个元素,则复制省略将关闭。模板函数可以这样写:
template<typename T, typename ... Args> T noelide(Args ... args) {
return (((T(&)[1])(T(args...)))[0]);
}
这种解决方案在大多数情况下效果很好。在下面的代码中,它生成三个复制构造函数调用 - 一个用于直接复制赋值,两个用于从函数返回的赋值。它在MSVC 2017中效果很好
#include <iostream>
class Clazz {
public: int q;
Clazz(int q) : q(q) { std::cout << "Default constructor " << q << std::endl; }
Clazz(const Clazz& cl) : q(cl.q) { std::cout << "Copy constructor " << q << std::endl; }
~Clazz() { std::cout << "Destructor " << q << std::endl; }
};
template<typename T, typename ... Args> T noelide(Args ... args) {
return (((T(&)[1])(T(args...)))[0]);
}
Clazz func(int q) {
return noelide<Clazz>(q);
}
int main() {
Clazz a = noelide<Clazz>(10);
Clazz b = func(20);
const Clazz& c = func(30);
return 0;
}
此方法适用于a
和b
情况,但使用案例c
执行冗余复制 - 应通过生存期扩展返回对临时的引用,而不是复制。
问题:如何修改noelide
模板以允许它与具有生命周期扩展的常量左值引用正常工作? 谢谢!
根据 N4140, 12.8.31:
。
复制/移动操作的这种省略(称为复制消除)是 在以下情况下允许(可以合并到 消除多个副本):
(31.1) — 在具有类返回类型的函数的 return 语句中, 当表达式是非易失性自动对象的名称时 (函数或捕获子句参数除外)具有相同的 CV-非限定类型作为函数返回类型,复制/移动 通过直接构造自动对象可以省略操作 到函数的返回值中
(31.3) — 当一个临时类对象尚未绑定到 引用 (12.2) 将被复制/移动到具有相同内容的类对象 CV-不合格类型,复制/移动操作可以省略 将临时对象直接构造到目标中 省略的复制/移动
因此,如果我理解正确,则只有在 return 语句是局部变量的名称时,才会发生复制省略。因此,例如,您可以通过返回"禁用"复制省略,例如return std::move(value)
......如果您不喜欢为此使用move
,您可以简单地将noelide
实现为static_cast<T&&>(...)
.
鉴于您的所有限制,这是不可能的。简单地说,因为该标准不提供关闭 RVO 优化的方法。
您可以通过违反其中一个要求来阻止强制应用 RVO,但无法可靠地阻止可选的允许优化。此时,您所做的一切都是更改语义或特定于编译器(例如-fno-elide-constructors
GCC 和 Clang 的选项)。
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 在C++程序中输入的文本文件将不起作用,除非文本被复制和粘贴
- 使用strcpy将char数组的元素复制到另一个数组
- 是否可以初始化不可复制类型的成员变量(或基类)
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- C++ Windows 驱动程序MSB3030无法复制该文件,因为它找不到
- 复制列表初始化的隐式转换的等级是多少
- 当从函数参数中的临时值调用复制构造函数时
- 有可能在Armadillo中复制MATLAB circshift方法吗
- 复制几乎为空的数组的最快方法
- 以下示例中如何避免代码复制?C++/库达
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 为什么需要复制构造函数,在哪些情况下它们非常有用
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- 为什么复制而不是移动数据元素?
- 文件系统:复制功能的速度秘诀是什么
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 为什么类中的ostringstream类型的成员会导致";调用隐含删除复制构造函数";错误
- 使lambda不可复制/不可移动