我的问题:这 std::move 真的需要吗?我的观点是这个 p_name 没有在构造函数的主体中使用,所以,也许语言中有一些规则默认使用移动语义?
当然是需要的。p_name
是左值,因此std::move
需要将其转换为右值并选择移动构造函数。
这不仅仅是语言所说的——如果类型是这样的:
struct Foo {
Foo() { cout << "ctor"; }
Foo(const Foo &) { cout << "copy ctor"; }
Foo(Foo &&) { cout << "move ctor"; }
};
copy ctor
如果您省略移动,则必须打印的语言要求。这里没有选项。编译器无法做到这一点。
是的,复制省略仍然适用。但不是您的情况(初始化列表),请参阅评论。
或者您的问题是否涉及我们为什么要使用这种模式?
答案是,当我们想要存储传递参数的副本时,它提供了一种安全模式,同时受益于移动,并避免参数的组合爆炸。
考虑这个包含两个字符串的类(即要复制的两个“重”对象)。
struct Foo {
Foo(string s1, string s2)
: m_s1{s1}, m_s2{s2} {}
private:
string m_s1, m_s2;
};
那么让我们看看在各种情况下会发生什么。
拿 1
string s1, s2;
Foo f{s1, s2}; // 2 copies for passing by value + 2 copies in the ctor
啊,这很糟糕。这里发生了 4 个副本,而真正需要的只有 2 个。在 C++03 中,我们会立即将 Foo() 参数转换为 const-refs。
拿 2
Foo(const string &s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
现在我们有
Foo f{s1, s2}; // 2 copies in the ctor
那好多了!
但是动作呢?例如,来自临时工:
string function();
Foo f{function(), function()}; // still 2 copies in the ctor
或者当明确地将左值移动到ctor中时:
Foo f{std::move(s1), std::move(s2)}; // still 2 copies in the ctor
那不是那么好。我们可以使用string
'move ctor 直接初始化Foo
成员。
拿 3
因此,我们可以为 Foo 的构造函数引入一些重载:
Foo(const string &s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
Foo(string &&s1, const string &s2) : m_s1{std::move(s1)}, m_s2{s2} {}
Foo(const string &s1, string &s2) : m_s1{s1}, m_s2{std::move(s2)} {}
Foo(string &&s1, string &&s2) : m_s1{std::move(s1)}, m_s2{std::move(s2)} {}
所以,好的,现在我们有了
Foo f{function(), function()}; // 2 moves
Foo f2{s1, function()}; // 1 copy + 1 move
好的。但是见鬼,我们得到了一个组合爆炸:现在每个参数都必须出现在它的 const-ref + rvalue 变体中。如果我们得到 4 个字符串呢?我们要写16个ctor吗?
拿4(好的)
让我们来看看:
Foo(string s1, string s2) : m_s1{std::move(s1)}, m_s2{std::move(s2)} {}
使用此版本:
Foo foo{s1, s2}; // 2 copies + 2 moves
Foo foo2{function(), function()}; // 2 moves in the arguments + 2 moves in the ctor
Foo foo3{std::move(s1), s2}; // 1 copy, 1 move, 2 moves
由于移动非常便宜,因此这种模式可以充分受益并避免组合爆炸。我们确实可以一路向下移动。
我什至没有触及异常安全的表面。
作为更一般性讨论的一部分,现在让我们考虑以下代码段,其中所有涉及的类通过值传递复制 s:
{
// some code ...
std::string s = "123";
AClass obj {s};
OtherClass obj2 {s};
Anotherclass obj3 {s};
// s won't be touched any more from here on
}
如果我的理解正确,您真的会希望编译器s
在最后一次使用时真正移开:
{
// some code ...
std::string s = "123";
AClass obj {s};
OtherClass obj2 {s};
Anotherclass obj3 {std::move(s)}; // bye bye s
// s won't be touched any more from here on.
// hence nobody will notice s is effectively in a "dead" state!
}
我告诉过你为什么编译器不能这样做,但我明白你的意思。从某种角度来看,这是有道理的——让s
live 比它最后一次使用的时间更长是无稽之谈。我想 C++2x 值得深思。