这个移动构造函数的实现是否抛弃了移动语义?

Does this implementation of a move constructor throw away move semantics?

本文关键字:移动 抛弃 语义 是否 实现 构造函数      更新时间:2023-10-16

如果我有一个像这样的C类:

class C {
    std::string s;
public:
    C(std::string&  s) : s(s) {}
    C(std::string&& s) : C(s) {}
};

我扔掉移动语义从string&&构造函数调用string&构造函数?

是的,你丢掉了你的move语义。我能提供的最直接的方法就是举例说明。这是我能想到的最理智的事情,所以我希望大家都清楚发生了什么。

考虑一下:

#include <iostream>
struct S
{
    int x;
    S() :x(1) { std::cout << __PRETTY_FUNCTION__ << 'n';}
    S(const S& s) : x(s.x) { std::cout << __PRETTY_FUNCTION__ << 'n';}
    S(S&& s) : x(std::move(s.x)) { ++x, s.x=0; std::cout << __PRETTY_FUNCTION__ << 'n';}
    ~S() { std::cout << __PRETTY_FUNCTION__ << ':' << x << 'n';}
};
class C {
    S s;
public:
    C(S& s) : s(s) { std::cout << __PRETTY_FUNCTION__ << 'n';}
    C(S&& s) : C(s) { std::cout << __PRETTY_FUNCTION__ << 'n'; }
    ~C() { std::cout << __PRETTY_FUNCTION__ << 'n';}
};
int main()
{
    C c{S{}};
}

S::S()
S::S(const S &)
C::C(S &)
C::C(S &&)
S::~S():1
C::~C()
S::~S():1

注意S两个实例是被构造的,它们中的都不是通过move构造的。任何被"移动"到预定目标的S都将为x打印0。没有做的事情。第一个SS{}的初始临时值;第二种是通过C(C&&)初始化项列表调用C(C&)生成的副本。当这段代码完成后,当我们开始运行析构函数链时,已经存在两个完整构造的S

现在看看这个,相同的代码,但是在s成员上使用了move-语义:

#include <iostream>
struct S
{
    int x;
    S() :x(1) { std::cout << __PRETTY_FUNCTION__ << 'n';}
    S(const S& s) : x(s.x) { std::cout << __PRETTY_FUNCTION__ << 'n';}
    S(S&& s) : x(std::move(s.x)) { ++x, s.x=0; std::cout << __PRETTY_FUNCTION__ << 'n';}
    ~S() { std::cout << __PRETTY_FUNCTION__ << ':' << x << 'n';}
};
class C {
    S s;
public:
    C(S& s) : s(s) { std::cout << __PRETTY_FUNCTION__ << 'n';}
    C(S&& s) : s(std::move(s)) { std::cout << __PRETTY_FUNCTION__ << 'n'; }
    ~C() { std::cout << __PRETTY_FUNCTION__ << 'n';}
};
int main()
{
    C c{S{}};
}

S::S()
S::S(S &&)
C::C(S &&)
S::~S():0
C::~C()
S::~S():2

仍然构造了两个S实例,但后者是通过move-construct,吸收了第一个实例。当我们开始销毁所有这些东西时,只有一个S实例仍然有效;另一个通过移动而死亡。

长话短说,如果你的目标是移动,你就是在射击自己。我希望能帮到你,不过如果没用的话,我完全愿意扔掉它。

当人们提到"完美转发"时,这不是指的。这只是一个调用复制构造函数的移动构造函数(我认为这就是您的意思)。这是完美的转发:

class Foo{
public:
    template<typename TBar> Foo(TBar&& bar): m_str(std::forward<TBar>(bar)){}
private:
    std::string m_str;
};

这里发生的事情是,由于模板参数演绎和引用折叠规则,TBar&&就是所谓的"通用引用",这意味着它可以将其类型解析为TBarTBar&TBar&&(也可以添加constvolatile)。std::forward就像它的名字所说的那样,将TBar解析到的任何内容(保留其推断的类型)"转发"给字符串的构造函数。