复制传递值参数的省略

Copy elision for pass-by-value arguments

本文关键字:参数 值参 复制      更新时间:2023-10-16

给定

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示例中,发生了两个副本。

  1. 构造函数中传递的临时对象(根据上面提到的标准可以省略)
  2. Box构造函数的初始值设定项列表中的副本(不能忽略)

你可以在这个演示中看到,复制构造函数只被调用了2次。

还要记住,因为标准允许在特定的上下文中进行复制省略优化,并不意味着编译器供应商有义务这样做。复制省略是唯一允许的优化形式,可以改变可观察到的副作用。因此,由于某些编译器不会在所有允许的情况下(例如,在调试模式下)执行复制省略,因此依赖复制/移动构造函数和析构函数副作用的程序是不可移植的

事实上,它可以,但并不意味着它肯定会。在这个演示中看到它,很明显你正在创建两个副本。提示,输出包含两个:

复制

复制