【问题标题】:Will any compiler actually ever elide these copies?任何编译器实际上会忽略这些副本吗?
【发布时间】:2016-02-25 16:15:48
【问题描述】:

给定

struct Range{
    Range(double from, double to) : from(from), to(to) {}
    double from;
    double to;
    // if it matters to the compiler, we can add more fields here to make copying expensive
};

struct Box{
    Box(Range x, Range y) : x(x), y(y) {}
    Range x;
    Range y;
};

someone saidBox box(Range(0.0,1.0),Range(0.0,2.0)) 中,编译器可以通过在box 中构造它们来完全避免复制Range 对象。

有没有编译器真正做到这一点?

我自己的尝试没有成功。

【问题讨论】:

  • 如果您的编译器是后裔,请放心。它会避开他们。
  • @101010 任何特定的现代编译器都不错吗?我做不到。
  • 您应该在没有重复问题的情况下对该答案发表评论。
  • @tennenrishin 编译器不会省略从参数到数据成员的副本,但您可以std::move 提高效率。
  • @tennenrishin 既然你问到关于删除 all 的副本,我会说答案是错误的。但我认为无论如何只能省略一组副本。所以有人可能会争辩说这种期望是错误的。

标签: c++ pass-by-value copy-elision


【解决方案1】:

有人就是我。所以让我澄清一下我的立场。

我从未说过在Box box(Range(0.0,1.0),Range(0.0,2.0)) 中,编译器可以通过在框内构造它们来避免复制 Range 对象完全。我说的是:

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

(31.3) -- 当一个临时类对象没有绑定到一个引用 (12.2) 将被复制/移动到具有相同类型的类对象 (忽略 cv 限定),复制/移动操作可以省略 将临时对象直接构造到 省略复制/移动。

是的,它可以,部分用于在构造函数中传递的临时对象(如上所述,根据标准可以省略)。我从来没有说过参数可以一直省略到Box构造函数的初始化列表。

毕竟,这种情况不符合任何可以按照标准应用复制省略的条件。

我还说过,即使某个上下文被限定为可以应用复制省略的上下文,编译器也没有义务遵循。如果您依赖这种效果,那么您的程序就不能被认为是可移植的。

【讨论】:

    【解决方案2】:

    编译器可以 - 而且通常会 - 删除从临时到参数的副本。编译器不能从参数中删除副本到成员。虽然在某些情况下可能在技术上可以省略这些副本,但未给予相关许可。该标准的部分是 12.8 [class.copy] 第 31 段,其中阐明了可以省略副本的 4 种情况(确切的规则有点不重要):

    1. 当返回一个命名的函数局部变量时,使用它的名字。
    2. throw 表达式中使用命名的函数局部变量时。
    3. 复制临时对象时。
    4. 按值捕获异常时。

    将命名参数作为参数传递给成员变量的构造显然不是这些情况。

    复制省略规则的基本背景是,在某些情况下,函数的声明足以确定何时使用对象。如果在构建时明确可以构建对象的位置,则可以省略它。构造函数的调用者不能仅根据构造函数的声明来确定对象将在哪里使用。

    【讨论】:

    • 作为对上述(非常好的)答案的补充,我想补充一点,保持控制的一种方法是使用(可能是 const)引用或指针参数,并且仅在复制时复制真的很有必要。这样,您就可以确切地知道发生了什么,并且不会产生不必要的副本,并且您基本上独立于编译器编写者在优化方面所做的工作。此外,您不必尝试解读难以阅读的标准文档。
    • @ErikAlapää 使用 const 引用会干扰移动语义;我想说在这里传递价值会更好。在这个特定的代码中,我们可以将x(x) 替换为x(std::move(x)) 等。结果(对于每个参数)2 个动作,其中 1 个可以省略;但如果更改为 const 引用,则有一份无法省略。
    • @MM 有趣 - 我开始了解 rvalue refs 和 std::move,但我仍然倾向于使用经过验证的旧式方法 ;) 但是,如果我使用指针,我仍然可以确保我尽量减少复制,因为我明确地必须复制才能获得副本。
    • @ErikAlapää:这里的关键问题是您可能只使用移动而不使用副本就可以逃脱。对于Ranges 而言,这并不重要:复制和移动是等价的。然而,假设参数更像std::vector<T>,而不是Range:当参数按值传递时,它是由移动或复制创建的,其中任何一个都可能被省略。由于已知该值不会被使用,否则它可以安全地被移出。这里的交易可能只是移动,在最坏的情况下复制和移动有保证的副本。
    • @DietmarKühl:好的,澄清一下。我已经多次讨论 std::move 和 rvalue 引用,但我仍然倾向于使用指针,因为我理解和控制 100% 发生的事情。我从标准中读到的关于在各种情况下复制省略的所有内容都是选项,而不是保证不会进行复制。在例如使用它高性能和/或实时系统,我希望得到标准和编译器的保证。
    猜你喜欢
    • 2020-01-02
    • 1970-01-01
    • 2015-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-16
    • 2013-09-10
    相关资源
    最近更新 更多