【问题标题】:Return std::tuple and move semantics / copy elision返回 std::tuple 并移动语义/复制省略
【发布时间】:2021-02-08 22:51:16
【问题描述】:

我有以下工厂函数:

auto factory() -> std::tuple<bool, std::vector<int>>
{
    std::vector<int> vec;
    vec.push_back(1);
    vec.push_back(2);

    return { true, vec };
}

auto [b, vec] = factory();

在 return 语句中 vec 被认为是 xvalueprvalue 并因此被移动或复制省略了?

我的猜测是否定的,因为编译器在列表初始化 return 语句中的 std::tuple 时,仍然不知道 vec 将被销毁。所以可能需要一个明确的 std::move :

auto factory() -> std::tuple<bool, std::vector<int>>
{
    ...
    return { true, std::move(vec) };
}

auto [b, vec] = factory();

真的需要吗?

【问题讨论】:

  • 你必须做return { true, std::move(vec) };
  • 是的,这里需要这样做以确保矢量将被移动。但是编译器大多足够聪明,可以优化副本
  • @bartop:编译器不允许“优化掉副本”。
  • @NicolBolas 实际上,只要可观察的行为没有改变,他们就可以做任何事情。会创建一个可观察的副本(前提是构造函数或析构函数中没有可观察到的副作用)?
  • @Aconcagua:是的,因为allocator::construct/destructvector 元素的复制构造函数都是可观察到的副作用。

标签: c++ c++17 move-semantics


【解决方案1】:

在 return 语句中 vec 被认为是一个 xvalue 或 prvalue,因此被移动或复制省略了?

vec始终是一个左值。即使在简单的情况下:

std::vector<int> factory() {
    std::vector<int> vec;
    return vec;
}

那是仍然返回一个左值。只是我们有special rules 表示当我们返回自动对象的名称时我们只是忽略了这种情况下的副本(以及另一个special rule 在复制省略不适用的情况下,但我们仍然尝试从左值移动)。

但这些特殊规则适用于return object; 案例,它们不适用于return {1, object}; 案例,无论它看起来多么相似。在您的代码中,这将进行复制,因为这就是您所要求的。如果你想移动,你必须这样做:

return {1, std::move(object)};

为了避免移动,你必须这样做:

auto factory() -> std::tuple<bool, std::vector<int>>
{
    std::tuple<bool, std::vector<int>> t;

    auto& [b, vec] = t;
    b = true;
    vec.push_back(1);
    vec.push_back(2);
    return t;
}

【讨论】:

  • 也许值得注意的是,在“避免移动”变体中,尾随返回类型可以完全省略...
  • 单线变体:return std::make_tuple(true, std::vector&lt;int&gt;({1, 2})); 现在不确定是否涉及构造返回类型,但...
  • 不过,我的单线器中更有趣的一点是,如果我重新引入了移动——我们最初想避免这种情况,即。 e.很可能我们不能将这个构造用于特定目的......
  • 非常感谢,非常清晰的解释。我用 GCC 和 MSVC 测试了所有四个版本,行为是预期的。@Aconcagua 的单行与 std::move 的版本相同(不同构造函数的一部分)。
猜你喜欢
  • 2018-12-15
  • 1970-01-01
  • 1970-01-01
  • 2020-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-13
相关资源
最近更新 更多