地图的移动构造函数

发布于 2024-09-29 04:16:03 字数 485 浏览 0 评论 0原文

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

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
};

这里是障碍: 当我试图为我的地图考虑移动向量时,我看不到一种避免为所有需要创建的链接分配新空间的方法,然后将它们的指针与 tmp Map 对象中的指针交换(作为 arg 传递)给我的搬迁演员)。
所以无论如何我都必须分配空间,不是吗?

I think I do understand "the basic IDEA" of move semantics, but now when I'm on the stage of implementing my own map I stopped and started to think about it when I was going to write a use case and walk through for move ctor of map. Correct me if I'm wrong but what I do understand how the whole business of move semantics works is that they suppose to help in avoiding unnecessary copying? Right? Now, take a map for example and just for the purpouse of this example assume that my map is modeled as:

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
};

And here is the snag:
When I'm trying to think of move ctor for my map I cannot see a way of avoiding allocating a new space for all those links which needs to be created and then their pointers swapped with those from a tmp Map object (passed as a arg to my move ctor).
So I do have to allocate space anyway, or don't I?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

绿萝 2024-10-06 04:16:04

可以使用旧的 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 构造函数是否抛出异常(不会调用其析构函数,因此不会自动进行清理)。如果数据的复制构造函数本身引发了你的麻烦,那你的麻烦就更大了。
一般来说,这些都不会发生,因为数据包含轻量对象(指针和数字)而不是重对象。

It is possible to implement move semantics with old C++ (not 0x) but it has to be done explicitly and is more tricky.

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
}

And X can be made non-copyable and non-assignable in the above, data can have items created on the heap and it is the destructor of X that is responsible for the cleanup. When data is reset by the move() function the parting X will have nothing to clean up because the ownership has been transferred.

Generally the data struct should be public within X but all its members should be private with X a friend. Users therefore do not therefore access anything in there directly.

Note that you are likely to "leak" if you call move() on X without attaching it to another X, thus if you call just func() above you would leak. You should also beware if the constructor of X from data throws (its destructor won't be called so no cleanup will happen automatically). If the copy-constructor of data itself throws you are in even more trouble.
Generally none of these should happen as data contains light stuff (pointers and numbers) and not heavy objects.

诗酒趁年少 2024-10-06 04:16:03

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

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

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

All you have to do is reassign the Link pointer, since all the other Link pointers are attached to it, they will now be part of the new Map.

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

This is assuming no other data members.

看春风乍起 2024-10-06 04:16:03

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

// 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;
}

移动赋值运算符背后的基本思想是如果执行交换后不再次检查 map2,则 swap(map1, map2) 与 map1 = map2 具有相同的可观察到的副作用。 回想一下,右值要么是右值,要么是x值。根据定义,客户端不能检查由纯右值指定的对象两次,因为评估纯右值总是导致创建新对象。观察此技巧的唯一方法是从 xvalue 移动,例如 std::move(map_variable),但 明显 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;
}

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

You need five operations total: the classic "big three" (copy constructor, copy assignment operator, destructor) and the two new move operations (move constructor, move assignment operator):

// 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;
}

The basic idea behind the move assignment operator is that swap(map1, map2) has the same observable side effect as map1 = map2 if you don't inspect map2 again after performing the swap. Recall that an rvalue is either a prvalue or an xvalue. By definition, a client cannot inspect an object designated by a prvalue twice, because evaluating a prvalue always leads to the creation of a new object. The only way to observe this trick is to move from an xvalue such as std::move(map_variable), but then it is obvious that map_variable is potentially modified.

If you want exception safe assignment even when copying, you can combine both the copy assignment operator (taking const Map&) and the move assignment operator (taking Map&&) to a generalized assignment operator (taking Map). Then you only need four operations total:

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

Note that this variant of the assignment operator takes its argument by value. If the argument is an lvalue, the copy constructor initializes that, and if the argument is an rvalue, the move constructor does the job. (Also note that specializing std::swap is unlikely to result in further significant performance gains if you already provide the move operations.)

与他有关 2024-10-06 04:16:03

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

Aside from the standard disclaimer not to reinvent existing containers, wouldn't it be sufficient to simply assign the root node pointer without doing any allocation?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文