我的理解是,在 C++17 中,以下代码片段旨在做正确的事情:
struct Instrument; // instrumented (non-trivial) move and copy operations
struct Base {
Instrument i;
};
struct Derived : public Base {};
struct Unrelated {
Instrument i;
Unrelated(const Derived& d): i(d.i) {}
Unrelated(Derived&& d): i(std::move(d.i)) {}
};
Unrelated test1() {
Derived d1;
return d1;
}
Base test2() {
Derived d2;
return d2; // yes, this is slicing!
}
也就是说,在 C++17 中,编译器应该将d1
和都d2
视为右值,以便在这两个返回语句中进行重载决策。然而,在 C++14 和更早的版本中,情况并非如此。操作数中的左值到右值转换return
应该仅在操作数完全是正确的返回类型时才适用。
此外,GCC 和 Clang 在这方面似乎都有令人困惑且可能存在错误的行为。在 Wandbox 上尝试上面的代码,我看到了这些输出:
GCC 4.9.3 and earlier: copy/copy (regardless of -std=)
Clang 3.8.1 and earlier: copy/copy (regardless of -std=)
Clang 3.9.1 and later: move/copy (regardless of -std=)
GCC 5.1.0 through 7.1.0: move/copy (regardless of -std=)
GCC 8.0.1 (HEAD): move/move (regardless of -std=)
所以这开始是一个工具问题,最后是“C++ 编译器的正确行为到底是什么?”。
我的工具问题是:在我们的代码库中,我们有几个地方说return x;
但意外地产生了副本而不是移动,因为我们的工具链是 GCC 4.9.x 和/或 Clang。我们希望自动检测这种情况并std::move()
根据需要插入。有没有简单的方法来检测这个问题?也许是一个整洁的检查或-Wfoo
我们可以启用的标志?
但当然现在我也想知道 C++ 编译器在这段代码上的正确行为是什么。这些输出是否表明 GCC/Clang 错误?他们正在研究吗?语言版本 ( -std=
) 应该重要吗?(我认为这应该很重要,除非通过缺陷报告更新了正确的行为,一直追溯到 C++11。)
这是受巴里回答启发的更完整的测试。我们测试了需要左值到右值转换的六种不同情况。
GCC 4.9.3 and earlier: elided/copy/copy/copy/copy/copy
Clang 3.8.1 and earlier: elided/copy/copy/copy/copy/copy
Clang 3.9.1 and later: elided/copy/move/copy/copy/copy
GCC 5.1.0 through 7.1.0: elided/copy/move/move/move/move
GCC 8.0.1 (HEAD): elided/move/move/move/move/move
ICC 17: elided/copy/copy/copy/copy/copy
ICC 18: elided/move/move/move/copy/copy
MSVC 2017 (wow): elided/copy/move/copy/copymove/copymove
在巴里的回答之后,在我看来,Clang 3.9+ 在所有情况下都做了技术上正确的事情;GCC 8+在所有情况下都做了理想的事情;总的来说,我应该停止教人们“只是return x
让编译器 DTRT”(或者至少用一个巨大的闪烁警告来教它),因为实际上编译器不会DTRT,除非你使用的是前沿(技术上非-符合)GCC。