我认为我理解移动语义的“基本IDEA”,但现在当我在实现我自己的地图的阶段时,我停下来并开始考虑它,当我打算写一个用例和走路时通过移动地图的ctor。如果我错了,请纠正我,但我知道移动语义的整个业务是如何工作的,他们认为有助于避免不必要的复制?对?现在,以地图为例,仅为本例的purpouse假设我的地图被建模为:
class Map
{
Link* impl_;//THIS IS A POINTER TO A LINK WHICH HAS A parent, left and right (of Link type)
Map(Map&& tmp);//move ctor
//unnecessary code ommited
};
这是障碍: 当我试图为我的地图移动ctor时,我看不到避免为所有需要创建的链接分配新空间的方法,然后将它们的指针与来自tmp Map对象的指针交换(作为arg传递)我的举动ctor) 所以我不得不分配空间,或者不是吗?
答案 0 :(得分:3)
您所要做的就是重新分配链接指针,因为所有其他链接指针都附加到它上面,它们现在将成为新地图的一部分。
Map(Map&& tmp) :impl_(tmp.impl_) { tmp.impl_ = nullptr; }
假设没有其他数据成员。
答案 1 :(得分:2)
除了不重新发明现有容器的标准免责声明之外,仅仅分配根节点指针而不做任何分配是不够的?
答案 2 :(得分:2)
总共需要五个操作:经典的“三巨头”(复制构造函数,复制赋值运算符,析构函数)和两个新的移动操作(移动构造函数,移动赋值运算符):
// destructor
~Map();
// copy constructor
Map(const Map& that);
// move constructor
Map(Map&& that)
{
impl_ = that.impl_;
that.impl_ = 0;
}
// copy assignment operator
Map& operator=(const Map& that);
// move assignment operator
Map& operator=(Map&& that)
{
using std::swap;
swap(impl_, that.impl_);
return *this;
}
移动赋值运算符背后的基本思想是,如果在执行交换后不再检查swap(map1, map2)
,map1 = map2
与map2
具有相同的可观察副作用。 Recall that an rvalue is either a prvalue or an xvalue。根据定义,客户端无法检查由prvalue指定的对象两次,因为评估prvalue 总是会导致创建新对象。观察这个技巧的唯一方法是从std::move(map_variable)
这样的xvalue移动,但是显然 map_variable
可能会被修改。
如果您希望在复制时进行异常安全分配,则可以组合复制赋值运算符(取const Map&
)和移动赋值运算符(取Map&&
)广义赋值运算符(取Map
)。那么你只需要四个操作:
// exception safe copy/move assignment operator
Map& operator=(Map that)
{
using std::swap;
swap(impl_, that.impl_);
return *this;
}
请注意,赋值运算符的此变体将其参数按值。如果参数是左值,则复制构造函数初始化that
,如果参数是右值,则移动构造函数执行该作业。 (另请注意,如果您已经提供移动操作,那么专门化std::swap
不太可能带来进一步显着的性能提升。)
答案 3 :(得分:1)
@FredOverflow here有一个很好的移动语义演练。
答案 4 :(得分:0)
可以使用旧的C ++(而不是0x)实现移动语义,但必须明确地完成并且更加棘手。
class X
{
// set access specifiers as required
struct data
{
// all the members go here, just plain easy-to-copy members
} m;
data move()
{
data copy(m);
m.reset(); // sets everything back to null state
return m;
}
explicit X( const data& d ) : m(d)
{
}
// other members including constructors
};
X::data func() // creates and returns an X
{
X x; // construct whatever with what you want in it
return x.move();
}
int main()
{
X x(func());
// do stuff with x
}
在上面可以使X不可复制且不可分配,数据可以在堆上创建项目,并且它是负责清理的X的析构函数。当move()函数重置数据时,由于所有权已被转移,分离X将无需清理任何内容。
通常,数据结构应该在X中公开,但其所有成员都应该是私人的,而X是朋友。因此,用户不会直接访问那里的任何内容。
请注意,如果在X上调用move()而不将其附加到另一个X,则可能会“泄漏”,因此如果您只调用上面的func(),则会泄漏。您还应该注意数据的X构造函数是否抛出(它的析构函数不会被调用,因此不会自动执行清理)。如果数据本身的拷贝构造函数抛出你就会遇到更多麻烦。 通常,这些都不会发生,因为数据包含轻量级(指针和数字)而不是重物。