这是类X
的移动构造函数:
X::X(X&& rhs)
: base1(std::move(rhs))
, base2(std::move(rhs))
, mbr1(std::move(rhs.mbr1))
, mbr2(std::move(rhs.mbr2))
{ }
这些是我警惕的事情:
rhs
移动两次,并且rhs
无法保证处于有效状态。对于base2
rhs
的相应成员移至mbr1
和mbr2
,但由于rhs
已经移出(并且再次确保不会出现有效状态)为什么要这样做?这不是我的代码。我在一个网站上找到了它。这个移动构造函数安全吗?如果是这样,怎么样?
答案 0 :(得分:16)
这取决于继承层次结构。但是这个代码很好的机会非常好。这是一个完整的演示,表明它是安全的(对于这个特定的演示):
#include <iostream>
struct base1
{
base1() = default;
base1(base1&&) {std::cout << "base1(base1&&)\n";}
};
struct base2
{
base2() = default;
base2(base2&&) {std::cout << "base2(base2&&)\n";}
};
struct br1
{
br1() = default;
br1(br1&&) {std::cout << "br1(br1&&)\n";}
};
struct br2
{
br2() = default;
br2(br2&&) {std::cout << "br2(br2&&)\n";}
};
struct X
: public base1
, public base2
{
br1 mbr1;
br2 mbr2;
public:
X() = default;
X(X&& rhs)
: base1(std::move(rhs))
, base2(std::move(rhs))
, mbr1(std::move(rhs.mbr1))
, mbr2(std::move(rhs.mbr2))
{ }
};
int
main()
{
X x1;
X x2 = std::move(x1);
}
应输出:
base1(base1&&)
base2(base2&&)
br1(br1&&)
br2(br2&&)
在这里您可以看到每个基地和每个成员只移动一次。
请记住:std::move
并没有真正动起来。它只是对右值的一个演员,仅此而已。
因此,代码转换为rvalue X
,然后将其传递给基类。假设基类看起来像我上面概述的那样,那么对于右值base1
和base2
进行隐式转换,它将移动构造这两个单独的基数。
此外,
请记住:移动的对象处于有效但未指定的状态。
只要base1
和base2
的移动构造函数不会进入派生类并更改mbr1
或mbr2
,那么这些成员就是仍处于已知状态,准备离开。没问题。
现在我确实提到可能会出现问题。这是如何:
#include <iostream>
struct base1
{
base1() = default;
base1(base1&& b)
{std::cout << "base1(base1&&)\n";}
template <class T>
base1(T&& t)
{std::cout << "move from X\n";}
};
struct base2
{
base2() = default;
base2(base2&& b)
{std::cout << "base2(base2&&)\n";}
};
struct br1
{
br1() = default;
br1(br1&&) {std::cout << "br1(br1&&)\n";}
};
struct br2
{
br2() = default;
br2(br2&&) {std::cout << "br2(br2&&)\n";}
};
struct X
: public base1
, public base2
{
br1 mbr1;
br2 mbr2;
public:
X() = default;
X(X&& rhs)
: base1(std::move(rhs))
, base2(std::move(rhs))
, mbr1(std::move(rhs.mbr1))
, mbr2(std::move(rhs.mbr2))
{ }
};
int
main()
{
X x1;
X x2 = std::move(x1);
}
在这个例子中,base1
有一个带有rvalue-something的模板化构造函数。如果此构造函数可以绑定到右值X
,并且此构造函数将从右值X
,然后移动,则会出现问题:
move from X
base2(base2&&)
br1(br1&&)
br2(br2&&)
解决此问题的方法(相对较少,但并非极为罕见),是forward<base1>(rhs)
而不是move(rhs)
:
X(X&& rhs)
: base1(std::forward<base1>(rhs))
, base2(std::move(rhs))
, mbr1(std::move(rhs.mbr1))
, mbr2(std::move(rhs.mbr2))
{ }
现在base1
看到右值base1
而不是右值X
,它将绑定到base1
移动构造函数(假设它存在),所以你再次得到:
base1(base1&&)
base2(base2&&)
br1(br1&&)
br2(br2&&)
再一次对世界都很好。
答案 1 :(得分:15)
这大约是隐式移动构造函数通常如何工作的方式:每个基础和成员子对象都是从rhs
的相应子对象移动构造的。
假设base1
和base2
是X
的基础,没有构造函数使用X
/ X&
/ X&&
/ {{ 1}},它写得很安全。传递给基类初始值设定项时,const X&
将隐式转换为std::move(rhs)
(base1&&
}。
编辑:当我在一个完全匹配base2&&
的基类中有一个模板构造函数时,这个假设实际上已经咬了我几次。明确地执行转换会更安全(虽然非常迂腐):
X&&
甚至只是:
X::X(X&& rhs)
: base1(std::move(static_cast<base1&>(rhs)))
, base2(std::move(static_cast<base2&>(rhs)))
, mbr1(std::move(rhs.mbr1))
, mbr2(std::move(rhs.mbr2))
{}
如果没有其他基类或成员而不是X::X(X&& rhs)
: base1(static_cast<base1&&>(rhs))
, base2(static_cast<base2&&>(rhs))
, mbr1(std::move(rhs.mbr1))
, mbr2(std::move(rhs.mbr2))
{}
/ X(X&&) = default;
/ base1
/ {我认为应该完全复制编译器为base2
隐式生成的内容{1}}。
再次编辑:C ++11§12.8/ 15描述了隐式成员复制/移动构造函数的确切结构。
答案 2 :(得分:2)
不,这将是可能未定义行为的示例。它可能会在很多时候起作用,但不能保证。 C ++允许它,但你必须确保不再尝试以这种方式再次使用rhs
。你试图在之后使用 它的rhs
三次它的内容已经被删除rvalue
引用被传递给一个可能会从它移动的函数(离开它)处于未定义的状态)。