23

给定

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;
};

假设我们运行Box box(Range(0.0,1.0),Range(0.0,2.0)).

Range启用优化的现代编译器能否在此构造过程中完全避免复制对象?(即构造Range里面的对象box开始?)

4

4 回答 4

29

实际上,对Range传递给构造函数的每个对象执行两个副本。第一次发生在将临时Range对象复制到函数参数中时。这可以根据 101010 的答案中给出的参考来省略。存在可以执行复制省略的特定情况。

第二次复制发生在将函数参数复制到成员(在构造函数初始化列表中指定)时。这不能被忽略,这就是为什么您仍然看到在 YSC 的答案中为每个参数制作了一个副本。

当复制构造函数有副作用时(例如 YSC 的答案中的打印),仍然可以对第一个副本执行复制省略,但必须保留第二个副本。

但是,如果编译器不改变观察到的程序行为,编译器总是可以自由地进行更改(这被称为“as-if”规则)。这意味着如果复制构造函数没有副作用并且删除构造函数调用不会改变结果,编译器甚至可以自由删除第二个副本。

您可以通过分析生成的程序集看到这一点。在这个例子中,编译器不仅优化了副本,甚至优化了Box对象本身的构造:

Box box(Range(a,b),Range(c,d));
std::cout << box.x.from;

生成与以下相同的程序集:

std::cout << a;
于 2015-11-23T14:23:27.370 回答
5

它应该,但我无法使它工作(现场示例)。编译器可能会检测到构造函数的副作用并决定不使用复制省略。

#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
于 2015-11-23T13:19:57.830 回答
4

是的,它可以,特别是这种复制省略上下文属于标准的12.8/p31.3 复制和移动类对象 [class.copy]中指定的复制省略标准:

(31.3) -- 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同类型的类对象(忽略 cv 限定)时,可以通过以下方式省略复制/移动操作将临时对象直接构造到省略的复制/移动的目标中。

任何血统编译器都会在此特定上下文中应用复制省略。但是,在 OP 示例中,发生了两个副本。

  1. 在构造函数中传递的临时对象(可以按照上面提到的标准省略)。
  2. 构造函数的初始化列表中的副本Box(不能省略)。

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

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

于 2015-11-23T13:21:33.530 回答
1

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

复制

复制

于 2015-11-23T13:30:59.600 回答