在实践中何时调用移动构造函数?

问题描述

我最近了解了移动构造函数,但很多在线资源并没有讨论复制省略。复制省略对我来说也很有意义,但它让我想知道什么时候会在没有超级人为的例子的情况下调用移动构造函数

来自一个流行的 SO 帖子,向我解释了移动语义https://stackoverflow.com/a/3109981/11188084

string b(x + y);                                
string c(some_function_returning_a_string());   

帖子说这两个都应该调用移动构造函数,因为它们接受临时变量。但是,这些实际上都没有调用移动构造函数我有 tested),相反,它们都只是执行复制省略,除非您通过显式编写 std::move 来强制这样做。

string b(std::move(x + y)); 
string c(std::move(some_function_returning_a_string()));

some_function_returning_a_string 返回 std::move(someString)。但你为什么要这样做?复制省略甚至比移动语义更高效。那么调用移动构造函数而不是复制省略的自然情况是什么?

在你指出我之前,我觉得 When Does Move Constructor get called? 的回答要么给出了人为的例子,要么其中一些只是做了复制省略。我有兴趣学习在实践中何时调用移动构造函数

这是测试的链接 https://godbolt.org/z/KfczbboTr

解决方法

在您的示例中,从对象移动是暂时的,但移动时并非总是如此。有时我们知道我们可以移动,因为移动的 from 对象将不再使用,即使它不是临时的。考虑这种类型:

struct foo {
    foo() = default;
    foo(foo&& f) noexcept {
        std::cout << "move\n";
    }
};

当您创建一个由 foo 组成的向量并且该向量重新分配时,它不会复制元素,但会移动它们。例如:

#include <iostream>
#include <vector>

int main() {
    std::vector<foo> v;
    v.resize(5);
    v.resize(v.capacity()+1); // force the vector to reallocate
}

output

move
move
move
move
move

复制或移动不可能被省略,因为元素在旧位置并且必须以某种方式到达内存中的新位置。

,

在使用 std::tuple<>std::pair<>std::variant<> 等包装器类型时,Move 构造始终在临时对象和 RValue 上被隐式调用。增加的间接级别可以防止复制省略,并且您最终会调用移动构造函数。

例如:

#include <tuple>
#include <iostream>

struct SomeType {
    SomeType() = default;
    SomeType(SomeType&&) {
      std::cout << "moved!\n";
    }
};

int main() {
  std::make_tuple(SomeType{},12,SomeType{});
}

输出以下内容:

moved!
moved!
,

对所发布问题的非常技术性的回答。这里会调用move构造函数,我觉得,代码一点都不做作。

struct Foo
{
    Foo(std::string str) : str(std::move(str)) { }
    std::string str;
}