复制传递值参数的省略
Copy elision for pass-by-value arguments
给定
struct Range{
Range(double from, double to) : from(from), to(to) {}
double from;
double to;
};
struct Box{
Box(Range x, Range y) : x(x), y(y) {}
Range x;
Range y;
};
假设我们运行CCD_ 1。
启用了优化的现代编译器是否可以避免在构建过程中完全复制Range
对象?(即在box
内部构造Range
对象,以?开头)
传递给构造函数的每个Range
对象实际上都有两个副本。第一种情况发生在将临时Range
对象复制到函数参数中时。根据101010的答案中给出的参考,这可以被忽略。在某些特定情况下可以执行副本省略。
第二次复制发生在将函数参数复制到成员中时(在构造函数初始化列表中指定)。这是无法消除的,这就是为什么您仍然可以在YSC的答案中看到每个参数都有一个副本。
当复制构造函数有副作用时(例如YSC的答案中的打印),仍然可以对第一个副本执行复制省略,但第二个副本必须保留。
然而,如果编译器不改变程序的观察行为,那么编译器总是可以自由地进行更改(这被称为"好像"规则)。这意味着,如果复制构造函数没有副作用,并且删除构造函数调用不会改变结果,则编译器可以自由删除第二个副本。
您可以通过分析生成的程序集来看到这一点。在这个例子中,编译器不仅优化了副本,甚至优化了Box
对象本身的构造:
Box box(Range(a,b),Range(c,d));
std::cout << box.x.from;
生成与相同的程序集
std::cout << a;
它应该,但我没能让它工作(示例)。编译器可能会检测到构造函数的副作用,并决定不使用副本省略。
#include <iostream>
struct Range{
Range(double from, double to) : from(from), to(to) { std::cout << "Range(double,double)" << std::endl; }
Range(const Range& other) : from(other.from), to(other.to) { std::cout << "Range(const Range&)" << std::endl; }
double from;
double to;
};
struct Box{
Box(Range x, Range y) : x(x), y(y) { std::cout << "Box(Range,Range)" << std::endl; }
Box(const Box& other) : x(other.x), y(other.y) { std::cout << "Box(const Box&)" << std::endl; }
Range x;
Range y;
};
int main(int argc, char** argv)
{
(void) argv;
const Box box(Range(argc, 1.0), Range(0.0, 2.0));
std::cout << box.x.from << std::endl;
return 0;
}
编译&运行:
clang++ -std=c++14 -O3 -Wall -Wextra -pedantic -Werror -pthread main.cpp && ./a.out
输出:
Range(double,double)
Range(double,double)
Range(const Range&)
Range(const Range&)
Box(Range,Range)
1
可以,特别是这种复制省略上下文属于标准的复制和移动类对象[class.copy]中指定的复制省略标准
(31.3)--当一个未绑定到引用的临时类对象(12.2)将被复制/移动到具有相同类型的类对象(忽略cv资格),复制/移动操作可以通过将临时对象直接构建到省略了复制/移动。
任何下降编译器都会在此特定上下文中应用副本省略。然而,在OP示例中,发生了两个副本。
- 构造函数中传递的临时对象(根据上面提到的标准可以省略)
Box
构造函数的初始值设定项列表中的副本(不能忽略)
你可以在这个演示中看到,复制构造函数只被调用了2次。
还要记住,因为标准允许在特定的上下文中进行复制省略优化,并不意味着编译器供应商有义务这样做。复制省略是唯一允许的优化形式,可以改变可观察到的副作用。因此,由于某些编译器不会在所有允许的情况下(例如,在调试模式下)执行复制省略,因此依赖复制/移动构造函数和析构函数副作用的程序是不可移植的
事实上,它可以,但并不意味着它肯定会。在这个演示中看到它,很明显你正在创建两个副本。提示,输出包含两个:
复制
复制
- 如何用参数值调用函数(仅在运行时已知)
- 如何将不同模板参数值但相同类型的值放入这种类型的变量中?C++
- 如何使用 C++ 解析具有参数名称和参数值的 XML
- 解析参数值 - 字符串和链接值(类型字符串的链接值)与(特定类型的)变量
- 如何将非可变参数值传递给 fmt::format?
- 用默认参数值和空参数列表区分构造函数
- 是否可以在尾随返回类型语法中直接使用参数值(不是其类型,而是值本身)
- 如何创建可变参数模板函数来移动参数值并处理左值和右值
- C++17 使用选定的构造函数在堆栈中构造数组(每个数组条目的构造函数参数值相同)
- 什么是包裹着色器参数值的类的好设计模式
- 是默认模板模板参数值推导的上下文
- 使用多个参数值调用模板函数
- 在解析分配给默认参数值的重载函数时,要考虑哪组函数
- 默认参数值错误 [Visual C++ 2008 中的错误?
- 根据输入参数值调用不同的基类构造函数
- 正在获取第n个可变参数值(不是类型)
- 传递将默认参数值作为模板参数的函数
- 参数值的自动模板函数专用化
- 临时参数值何时超出范围
- 为什么我们可以检测 SFINAE 中 operator() 的默认参数值的存在,而不是自由函数和 PMF 的默认参数值