C++ 优化编译器可以添加std::move吗?

C++ 优化编译器可以添加std::move吗?,c++,c++11,optimization,move-semantics,rvalue,C++,C++11,Optimization,Move Semantics,Rvalue,如果编译器可以证明左值不再被使用,那么它能自动进行左值到右值的转换吗?下面是一个例子来阐明我的意思: void Foo(vector<int> values) { ...} void Bar() { vector<int> my_values {1, 2, 3}; Foo(my_values); // may the compiler pretend I used std::move here? } void Foo(向量值){…} 空条(){ 向量my_值

如果编译器可以证明左值不再被使用,那么它能自动进行左值到右值的转换吗?下面是一个例子来阐明我的意思:

void Foo(vector<int> values) { ...}

void Bar() {
  vector<int> my_values {1, 2, 3};
  Foo(my_values);  // may the compiler pretend I used std::move here?
}
void Foo(向量值){…}
空条(){
向量my_值{1,2,3};
Foo(my_values);//编译器可以假装我使用了std::move here吗?
}
如果将
std::move
添加到注释行中,则可以将向量移动到
Foo
的参数中,而不是复制。然而,正如所写的,我没有使用
std::move


静态证明my_值不会在注释行之后使用非常容易。那么,是允许编译器移动向量,还是需要它来复制它?

编译器的行为必须像从
向量
复制到调用
Foo
一样

如果编译器能够证明存在一个有效的抽象机器行为,并且没有可观察到的副作用(在抽象机器行为中,而不是在真正的计算机中!),这涉及到将
std::vector
移动到
Foo
,那么它可以做到这一点

在你上面的例子中,这个(移动没有抽象机器可见的副作用)是正确的;然而,编译器可能无法证明这一点

复制
std::vector
时可能观察到的行为是:

  • 在元素上调用复制构造函数。无法观察到使用
    int
    执行此操作
  • 在不同的时间调用默认的
    std::allocator
    。这将调用
    ::new
    ::delete
    (可能是1)在任何情况下,
    ::new
    ::delete
    在上述程序中都没有被替换,因此您不能在标准下遵守这一点
  • 在不同对象上多次调用
    T
    的析构函数。使用
    int
    时不可见
  • 调用
    Foo
    后,向量
    为非空。没有人检查它,所以它是空的就好像它不是
  • 外部向量元素与内部向量元素不同的引用、指针或迭代器。在
    Foo
    之外,没有引用、向量或指针指向向量的元素
虽然你可能会说,“但是如果系统内存不足,向量很大,那么这不是可以观察到的吗?”

抽象机器没有“内存不足”的情况,它只是由于非约束的原因,有时分配失败(抛出
std::bad_alloc
)。它不失败是抽象机器的一种有效行为,只要内存的不存在没有明显的副作用,通过不分配(实际的)内存(在实际的计算机上)而不失败也是有效的

稍微多一点的玩具箱:

int main() {
  int* x = new int[std::size_t(-1)];
  delete[] x;
}
虽然这个程序显然分配了太多的内存,但编译器可以不分配任何东西

我们可以走得更远。甚至:

int main() {
  int* x = new int[std::size_t(-1)];
  x[std::size_t(-2)] = 2;
  std::cout << x[std::size_t(-2)] << '\n';
  delete[] x;
}
x
没有
std::move
,但是它被移动到返回值中,并且该操作可以省略(
x
并且返回值可以转换为一个对象)

这:

std::vector foo(){ 向量x={1,2,3,4}; 返回std::move(x); }
阻止省略,如下所示:

std::vector<int> foo(std::vector<int> x) {
  return x;
}
std::vector foo(std::vector x){
返回x;
}
我们甚至可以阻止这一行动:

std::vector<int> foo() {
  std::vector<int> x = {1,2,3,4};
  return (std::vector<int> const&)x;
}
std::vector foo(){ 向量x={1,2,3,4}; 返回(std::vector const&)x; } 甚至:

std::vector<int> foo() {
  std::vector<int> x = {1,2,3,4};
  return 0,x;
}
std::vector foo(){ 向量x={1,2,3,4}; 返回0,x; } 因为隐式移动的规则是故意脆弱的。(
0,x
是使用备受诟病的
操作符)


现在,依靠像最后一个
这样的情况下不发生的隐式移动,不建议基于
的情况:标准委员会已经将隐式复制情况更改为隐式移动,因为隐式移动被添加到语言中,因为他们认为它是无害的(函数返回类型
a
,带有
a(B&&)
ctor,return语句是
return b;
其中
b
b
类型;在执行复制的C++11版本中,现在它执行移动。)不能排除隐式移动的进一步扩展:显式强制转换到
常量&
可能是现在和将来防止隐式移动的最可靠的方法。

在这种情况下,编译器可能会移出
my\u值
。这是因为这不会导致可观察行为的差异

引用C++标准的可观察行为定义:< /P> 一致性实施的最低要求是:

  • 对易失性对象的访问严格按照抽象机器的规则进行评估
  • 在程序终止时,写入文件的所有数据应与根据抽象语义执行程序可能产生的结果之一相同
  • 交互设备的输入和输出动态应以这样一种方式发生,即在程序等待输入之前,提示输出实际上已交付。交互设备的构成由实现定义

稍微解释一下:这里的“文件”包含标准输出流,对于未由C++标准定义的函数调用(例如操作系统调用,或调用第三方库),必须假定这些函数可能写入文件,由此推论,非标准函数调用也必须被视为可观察的行为

但是,您的代码(如您所示)没有
volatile
变量,也没有对非标准函数的调用。因此,两个版本(移动或不移动)必须具有相同的可观察行为,因此编译器可以执行其中之一(甚至可以完全优化函数,等等)

公共关系
std::vector<int> foo() {
  std::vector<int> x = {1,2,3,4};
  return (std::vector<int> const&)x;
}
std::vector<int> foo() {
  std::vector<int> x = {1,2,3,4};
  return 0,x;
}