自动检测C++14 "return should use std::move"情况

Automatically detect C++14 "return should use std::move" situation

本文关键字:情况 move std should C++14 return 自动检测 use      更新时间:2023-10-16

我的理解是,在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中,编译器应同时将d1d2视为RVALUE,以便在这两个返回语句中过载分辨率。但是,在C 14和更早的时候,情况并非如此。return操作数中的LVALUE到RVALUE转换仅在操作数正好正确的返回类型时适用。

此外,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()。有什么简单的方法可以检测此问题吗?也许我们可以启用clang-tidy检查或-Wfoo标志?

但是,当然,现在我也想知道C 编译器在此代码上的正确行为是什么。这些输出是否指示GCC/CLANG错误?他们正在工作吗?语言版本(-std=(应该很重要吗?(我认为这应该很重要,除非通过缺陷报告更新了正确的行为,直到C 11。(

这是一个受巴里答案启发的更完整的测试。我们测试了六种不同的情况,其中应需要LVALUE转换转换。

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"(或至少要用巨大的闪烁警告教(,因为在实践中,编译器将 not> not> dtrt,除非您使用出血边缘(技术不合格(gcc。

正确的行为是移动/复制。您可能只想写一个clang-tidy的支票。


C 17中的措辞是[class.copy.elision]/3,C 14中的措辞为[class.copy]/32。特定的单词和格式不同,但规则是相同的。

在C 11中,规则措辞为[class.copy]/32,并与复制责任规则绑定,自动存储本地变量的例外是在CWG 1579中作为缺陷报告。该缺陷报告之前的编译器将以复制/复制的形式行为。但是,由于缺陷报告针对C 11,实施措辞更改的编译器将在所有标准版本中实施它。

使用C 17措辞:

在以下复制委托上下文中,可以使用移动操作代替复制操作:

  • 如果返回语句中的表达式是(可能是括号内的(ID表达式,该命名为一个自动存储持续时间的对象,或者在主体中声明的自动存储持续时间或最内置的封闭函数或lambda-expression的参数 - 删除范围,或
  • [...]

超载分辨率首先要选择副本的构造函数,就像由rvalue指定的对象一样。如果第一个过载分辨率失败或未执行,则或所选构造函数的第一个参数的类型不是对对象类型的RVALUE引用(可能是cv qualified(,则执行过载分辨率。同样,将物体视为lvalue。

in:

Unrelated test1() {
    Derived d1;
    return d1;
}

我们遇到了第一个子弹,因此我们尝试使用Derived类型的rvalue复制Unrelated,这给我们提供了Unrelated(Derived&& )。符合突出显示的标准,因此我们使用它,结果是移动。

in:

Base test2() {
    Derived d2;
    return d2;  // yes, this is slicing!
}

我们再次遇到第一个子弹,但是超载分辨率将找到Base(Base&& )。所选构造函数的第一个参数是不是Derived(可能是CV -qualified(的RVALUE引用,因此我们再次执行超载分辨率 - 最终复制。