0

我想我确实了解移动语义的“基本思想”,但是现在当我在实现自己的地图的阶段时,当我要编写一个用例并遍历移动时,我停下来开始思考它地图的ctor。如果我错了,请纠正我,但我确实理解移动语义的整个业务是如何工作的,他们认为它们有助于避免不必要的复制?对?现在,以地图为例,仅出于本示例的目的,假设我的地图建模为:

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 地图中的指针交换对象(作为 arg 传递给我的移动 ctor)。
所以无论如何我都必须分配空间,不是吗?

4

4 回答 4

3

您所要做的就是重新分配 Link 指针,因为所有其他 Link 指针都附加到它,它们现在将成为新 Map 的一部分。

Map(Map&& tmp) :impl_(tmp.impl_) { tmp.impl_ = nullptr; }

这是假设没有其他数据成员。

于 2010-10-27T14:59:01.447 回答
3

您总共需要五个操作:经典的“三巨头”(复制构造函数、复制赋值运算符、析构函数)和两个新的移动操作(移动构造函数、移动赋值运算符):

// 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)相同的可观察到的副作用。回想一下 rvalue 是 prvalue 或 xvalue。根据定义,客户端不能两次检查由纯右值指定的对象,因为评估纯右值总是会导致创建新对象。观察这个技巧的唯一方法是从一个 xvalue 移动,例如,但很明显它可能会被修改。map1 = map2map2std::move(map_variable)map_variable

如果即使在复制时也希望进行异常安全赋值,您可以复制赋值运算符 (taken const Map&) 和移动赋值运算符 (taking Map&&) 组合成通用赋值运算符 (taking Map)。那么你总共只需要四个操作:

// exception safe copy/move assignment operator
Map& operator=(Map that)
{
    using std::swap;
    swap(impl_, that.impl_);
    return *this;
}

请注意,赋值运算符的这个变体通过 value获取其参数。如果参数是左值,则复制构造函数初始化that,如果参数是右值,则移动构造函数完成这项工作。(另请注意,std::swap如果您已经提供了移动操作,则专门化不太可能导致进一步的显着性能提升。)

于 2010-10-27T15:32:34.103 回答
2

除了不重新发明现有容器的标准免责声明之外,仅分配根节点指针而不进行任何分配就足够了吗?

于 2010-10-27T14:59:20.027 回答
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() 函数重置数据时,partition X 将没有任何东西需要清理,因为所有权已经转移。

一般来说,数据结构在 X 中应该是公共的,但它的所有成员应该是私有的,X 是朋友。因此,用户不会直接访问其中的任何内容。

请注意,如果您在 X 上调用 move() 而不将其附加到另一个 X,则可能会“泄漏”,因此如果您仅在上面调用 func() 就会泄漏。如果 X from data 的构造函数抛出,你也应该小心(它的析构函数不会被调用,所以不会自动进行清理)。如果数据的复制构造函数本身给你带来了更大的麻烦。通常这些都不应该发生,因为数据包含轻的东西(指针和数字)而不是重的对象。

于 2010-10-27T15:27:47.960 回答