在实践中什么时候调用移动构造函数?

When is a move constructor called in practice?

我最近学习了移动构造函数,但是很多在线资源都没有谈论复制省略。复制省略对我来说也很有意义,但它让我想知道如果没有超级人为的例子,什么时候会调用移动构造函数。

来自一个流行的 SO post,它向我解释了移动语义

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

post 说这两个都应该调用移动构造函数,因为它们接收临时对象。但是,其中 none 实际上调用了移动构造函数(我有 tested),相反它们都只是进行复制省略,除非您通过显式编写 std::move.[=19 来强制它这样做=]

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

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

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

这里有一个 link 用于测试 https://godbolt.org/z/KfczbboTr

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

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

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

例如:

#include <tuple>
#include <iostream>

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

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

输出如下:

moved!
moved!

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

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

无法删除复制或移动,因为元素位于旧位置并且必须以某种方式到达内存中的新位置。