保证省略和链式函数调用

18

假设我有以下类型:

struct X {
    X& operator+=(X const&);
    friend X operator+(X lhs, X const& rhs) {
        lhs += rhs;
        return lhs;
    }
};

我有这个声明(假设所有命名变量都是类型 X 的左值):

X sum = a + b + c + d;
在C++17中,我对这个表达式会执行多少次拷贝和移动操作有哪些保证?非保证的省略呢?

在C ++ 17中,关于这个表达式将会执行多少次复制和移动操作,有哪些担保措施?非担保的省略呢?

2个回答

19

这将执行1次拷贝构造和3次移动构造。

  1. a 复制一份并将其绑定到 lhs
  2. 从第一个 + 中移动构造 lhs
  3. 第一个 + 的返回值将通过省略绑定到第二个 + 的按值 lhs 参数上。
  4. 第二个 lhs 的返回值将产生第二次移动构造。
  5. 第三个 lhs 的返回值将产生第三次移动构造。
  6. 从第三个 + 返回的临时对象将在 sum 处构造。

针对上述每一次移动构造,都有另一次可选省略的移动构造。因此你仅能保证有1次拷贝和6次移动。但实际上,除非你使用了 -fno-elide-constructors,否则你只会有1次拷贝和3次移动。

如果在这个表达式之后不再引用 a,你可以进一步优化:

X sum = std::move(a) + b + c + d;

导致0份复制和4次移动(使用-fno-elide-constructors时有7次移动)。

以上结果已经得到使用带有插入式复制和移动构造函数的X进行确认。


更新

如果您对优化此代码的不同方法感兴趣,可以从在X const&X&&上重载lhs开始:

friend X operator+(X&& lhs, X const& rhs) {
    lhs += rhs;
    return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
    auto temp = lhs;
    temp += rhs;
    return temp;
}

这样可以将复制次数和移动次数减少到1和2。如果你愿意限制客户端不通过引用捕获+的返回值,那么可以像这样从其中一个重载中返回X&&

friend X&& operator+(X&& lhs, X const& rhs) {
    lhs += rhs;
    return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
    auto temp = lhs;
    temp += rhs;
    return temp;
}

让您只剩下1份复制和1次移动。请注意,在这个最新的设计中,如果您的客户做到了这一点:

X&& x = a + b + c;

那么x就是悬空引用(这就是为什么std::string不会这样做的原因)。


所以不能省略链接吗?比如将a+b的返回值直接构造到operator+(??, c)中? - Barry
2
我甚至认为省略lhs的返回值是不合法的。然而,我也不认为任何编译器的编写者有动机使其合法化(毕竟没有人尝试过这样做)。 - Howard Hinnant
为什么它可能是非法的?它未来会合法化吗? - Tomilov Anatoliy
@Orient:我的意思是标准不允许这样做。据我所知,没有人提出过这个想法。我猜测原因是没有人知道如何实现它。但这只是我的猜测,我不是编译器专家。 - Howard Hinnant

9

好的,让我们从这里开始:

X operator+(X lhs, X const& rhs) {
    lhs += rhs;
    return lhs;
}

这将始终引发从参数到返回值对象的复制/移动。C++17没有改变这一点,也没有任何形式的省略可以避免此复制。
现在,让我们看看您表达式的一部分:a + b。由于operator+的第一个参数是按值传递的,因此必须将a 复制到其中。所以那是一份副本。返回值将被复制到返回prvalue中。所以那是1个复制和1个移动/复制。
现在,下一部分:(a + b) + c
C++17意味着从a + b返回的prvalue将用于直接初始化operator+的参数。这不需要复制/移动。但是从此返回的返回值将从该参数复制。所以那是1个复制和2个移动/复制。
对于最后一个表达式重复此过程,那是1个复制和3个移动/复制。sum将从prvalue表达式初始化,因此不需要进行复制。
您的问题似乎真正关心的是C++17中是否仍然排除了省略的参数。因为它们在之前的版本中已经被排除了。这不会改变; 排除参数的原因仍然有效。
“保证省略”仅适用于 prvalue。如果它有一个名称,它不能是prvalue。

其中一些将是移动 - 你的意思是1个副本和3个移动吗? - Barry
1
换句话说,尼古尔关于参数复制的评论是:仅通过值传递参数只有在进入时进行复制省略才有帮助,并且仅在使用参数时才有意义,即将其std::move()到其他地方,例如:X rc(std::move(lhs)); rc += rhs; return rc; - Dietmar Kühl
@Barry:是的,我忘记了从返回本地变量的移动是自动的。 - Nicol Bolas
1
我猜如果整个东西能编译成等价的 X sum(a); sum += b; sum += c; sum += d; 那就很酷了。不过这还是相当困难的。 - Barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接