微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

emplacement 函数通过就地构造而不是通过赋值向容器添加新元素是什么意思?

如何解决emplacement 函数通过就地构造而不是通过赋值向容器添加新元素是什么意思?

Effective Modern C++ 的第 41 条中,Scott Meyers 提到了这种差异及其对就插入而言的就位效率的影响。

我对此有些怀疑,但在提出问题之前,我需要了解这两种添加元素的方式之间的区别。

考虑书中的代码示例:

std::vector<std::string> vs;
// adding some elements to vs
vs.emplace(v.begin(),"xyzzy");

很明显,在// adding some elements to vs之后,可能是

  • vs.capacity() == vs.size()
  • vs.capacity() > vs.size()

相应地,

  • vs 必须重新分配,并且 vs 中所有预先存在的元素(那些名为 vs[0]vs[1]、... 在重新分配发生之前)必须重新分配移动构造到新的内存位置( 的新位置vs[1],vs[2],...)
  • vs 必须 vs.resize(vs.size() + 1) 并且所有预先存在的元素都必须移动分配到下一个索引,显然是向后处理,从最后一个元素到第一个元素。

(显然我提到了移动操作,因为 std::string 提供了 noexcept 移动操作。如果不是这种情况,那么上面的场景略有不同,将使用复制操作。)

>

然而,进入我问题的关键,紧接在上面的代码之后,这本书读到(我的斜体

[…] 很少有实现会构建添加std::string vs[0]占用的内存中。相反,他们会移动分配价值到位。 […]

在完成房间以容纳新元素之后,两种情况是什么?

  • 如果 emplace 通过移动赋值添加元素,这意味着它 v[0] = std::string{strarg}; where strarg == "xyzzy",对吗?
  • 但是构造由 v[0] 占据的元素的另一种情况是什么?是展示位置new吗?它会是什么样子?我想它应该类似于 Placement new here 部分的代码块,但我不确定在这种情况下它会是什么样子。

解决方法

实现 emplace 的方法有很多种,而且标准对于实现必须如何实现非常宽松。

给定一个指向由 std::allocator_traits<allocator_type>::allocate 分配的某处的指针,向量构造新对象的唯一方法是使用 std::allocator_traits<allocator_type>::construct。对于默认分配器,这将调用placement new。

现在,如果确实发生了重新分配,放置新元素的明显方法是调用 allocator_traits::construct(get_allocator(),ptr,std::forward<Args>(args...))。这将等同于 new (ptr) std::string("xyzzy")。但请注意,所有其他元素也通过 allocator_traits::construct(get_allocator(),std::move(old_ptr)) 移动构造到新缓冲区。

如果没有发生重新分配,大多数实现只会用 value_type(std::forward<Args>(args...)) 构造一个元素并从中移动分配(相当于 v[0] = std::string("xyzzy"))。这就是 libstdc++ 所做的。

或者,可以通过allocator_traits::destroy(默认分配器为v[0] = std::string("xyzzy"))销毁对象,而不是移动构造(&v[0])->~value_type(),然后可以通过{{1 }}。这似乎更难实现,因为需要特别小心以确保如果移动构造函数抛出该元素不会被破坏两次,这可能就是为什么只有“少数实现”会这样做的原因。

顺便说一句,没有强异常保证,因此不必使用 allocator_traits::construct,并且移动构造函数可能始终被调用,即使移动构造函数不是 move_if_noexcept

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。