问题 编译器可以自动生成std :: move以最后使用左值吗?


像这样的代码经常出现在r值引用文章中:

戴夫艾布拉姆斯:用右值参考来移动它

void g(X);

void f()
{
    X b;
    g(b);              // still need the value of b
    …
    g( std::move(b) ); // all done with b now; grant permission to move
}

编译器是否可以自动生成此优化,即检测l值是否会被破坏并且可能会被移除,或者这将违反标准,假设一般情况编译器不知道如何是移动,复制或破坏为X类实现?

如果允许这样的优化,它是否由某些编译器在实践中执行?


8844
2018-03-13 13:52


起源

是。不,也许吧。编译器可以执行它,只要它不违反标准(所谓的“as-if规则”)。 - R. Martinho Fernandes
机器人说的是什么,虽然答案一般都是“不”。但是,如果返回局部变量,则可以保证自动移动(以某些条件为模,例如返回类型与局部变量的类型相同)。 - Xeo
@ R.MartinhoFernandes嗯,这个问题 特别 询问这是否会违反标准,所以我不太清楚你的评论会增加什么。 (除非你评论后编辑了?) - us2012
这可能会通过使用不同的重载来改变程序的可观察行为 g()。 - juanchopanza
@ us2012如果违反标准会违反标准(IOW,这个例子留下了太多未定义的答案,不是“它取决于”) - R. Martinho Fernandes


答案:


不,请考虑:

using X = std::shared_ptr<int>;
void g(X);
void f() {
    X b = std::make_shared<int>();
    int &i = *b;
    g(b);              // last use of 'b'
    i = 5;
}

通常,编译器不能假设改变副本,移动和析构函数的语义 X 将是一个合理的更改,而不对所有使用的代码进行分析 b (即整个 fg,以及其中使用的所有类型)。

实际上,在某些情况下,可能需要进行全程序分析:

using X = std::shared_ptr<std::lock_guard<std::mutex>>;
std::mutex i_mutex;
int i;
void g(X);
void f() {
    X b = std::make_shared<std::lock_guard<std::mutex>>(i_mutex);
    g(b);              // last use of 'b'
    i = 5;
}

如果 b 移动后,这会引发与其他线程同步访问的数据竞争 i 运用 i_mutex


7
2018-03-13 14:07



很好的反例,它表明编译器很难证明l值的某些特定用法实际上是它在销毁之前的最后一个,因为它可能仍然有一个指针或引用它。 - Suma
@Suma它不只是访问左值; b 可能是一名互斥锁。看看我上面的新例子。 - ecatmur
我认为转义分析,类似于在GC运行时中用来证明可以从堆栈中分配内存的转义分析,可以用来证明任何无效的引用 b 调用后可以访问内存 g。具有自定义构造函数的对象可能会被自动拒绝,但对于简单的事情来说可能性很小 struct { int x, int y, int z } escale分析可靠......我认为。 - Zbigniew Zagórski
@ZbigniewZagórski肯定,但如果类型足够简单,可以使逃逸分析工作,那么编译器可以优化复制/移动。在类型足够复杂以至于难以跟踪其操作的副作用的情况下,需要精确地遵循RVO样式规则。 - ecatmur


可以 编译器做到了吗?仅作为显式语言扩展,因为标准不允许它们在没有它的情况下进行这样的优化。

应该 他们这样做?没有。的意思 g(b) 应该基于。的定义 g 和 bg 应该是一些可调用的类型,它有一个过载,需要一些东西 b 可以隐式转换成。给定访问所有可用的定义 gs,以及。的定义 b,你应该能够确定 究竟 将调用什么函数。

现在允许这种“优化”意味着这是不可能的。 g(b)  威力 执行移动,它可能不会,具体取决于具体位置 g(b) 碰巧是在一个功能。这不是一件好事。

return 被允许逃脱它,但只是因为它仍然具有相同的含义。 return b 将永远尝试从 b 如果 b 是一个值类型,其生命周期仅限于函数的范围。


4
2018-03-13 14:05





(...)假设一个通用的情况,编译器不知道如何为X类实现移动,复制或破坏?

不,编译器不允许根据信仰进行优化。

为清楚起见,这个问题与复制省略无关:可以允许编译器删除副本,但是他们不能无限制地将副本更改为移动。


4
2018-03-13 14:00



嗯,有时他们是,例如,在复制省略的情况下(例如,返回值优化) - 在这种情况下,允许程序员以合理的方式实现复制构造函数。我想在这个特殊情况下,标准中没有任何内容允许编译器具有这种“信念”,这使得优化变得不可能。 - Suma
优化不是基于信仰。它基于这样一个事实,即标准明确允许他们忽略后果。 - Puppy