以下移动构造函数代码安全吗?
Is the following move constructor code safe?
这是类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
已经从中移动(同样,它不能保证处于有效状态),为什么这应该工作?
这不是我的代码。我在一个网站上找到了它。此移动构造函数安全吗?如果是这样,如何?
这大约是隐式移动构造函数通常的工作方式:每个基和成员子对象都是从rhs
的相应子对象移动构造的。
假设base1
和base2
是没有构造函数采用X
/X&
/X&&
/const X&
的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))
{}
我相信,如果除了base1
/base2
/mbr1
/mbr2
之外没有其他基类或成员,编译器应该完全复制编译器为X(X&&) = default;
隐式生成的内容。
再次编辑:C++11 §12.8/15 描述了隐式成员复制/移动构造函数的确切结构。
这取决于继承层次结构。 但是很有可能这段代码很好。 这是一个完整的演示,显示它是安全的(对于这个特定的演示):
#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
并没有真正移动。 这只是一个价值的演员,仅此而已。
因此,代码强制转换为右值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 Xn";}
};
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
有一个采用右值的模板化构造函数。 如果此构造函数可以绑定到右值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&&)
世界又是美好的。
不,这将是可能的未定义行为的一个例子。它可能在很多时候有效,但不能保证。C++允许这样做,但您必须确保不会再次尝试以这种方式使用rhs
。您正在尝试使用rhs
三次,因为它的胆量被扯掉了,它的rvalue
引用被传递给了一个可能从它移动的函数(使其处于未定义的状态)。
- 提供对不同类型的数据(建议、代码审查)的线程安全访问的类
- 实现在多线程代码中安全恢复的断点
- C++代码中的异常安全
- 编写"anti-lack of memory"异常安全代码
- 在代码的其他部分中对lock_gard和不使用相同的互斥锁是否安全?
- 我怎么知道C++编译器是否制作线程安全的静态对象代码
- 此代码安全吗?(链表,C++)
- 从 C# 到C++和返回的数组,没有不安全的代码
- 此代码是否容易受到 SQL 注入的攻击?我该如何使其安全
- 如何改进代码以生成安全的随机数
- 苹果安全传输代码错误
- 这段代码安全吗(为什么它没有崩溃)?
- 什么是 C# 安全代码在C++中的等效项
- 有没有更好的方法可以使此代码线程安全?线程局部静态似乎是一个生硬的工具
- 此代码是否对C++线程安全
- 如何使用 openMP 使此代码线程安全
- C 代码 - 覆盖率(或其他静态代码分析仪) 线程安全
- 异常安全代码和移动语义
- 在单元安全代码中处理文本零
- 泛型堆栈类的异常安全代码