Max*_*eap 16 c++ move vector unique-ptr c++11
我的一个函数将vector作为参数并将其存储为成员变量.我正在使用const引用,如下所述.
class Test {
 public:
  void someFunction(const std::vector<string>& items) {
   m_items = items;
  }
 private:
  std::vector<string> m_items;
};
但是,有时候items包含大量的字符串,所以我想添加一个支持移动语义的函数(或用新的函数替换它).
我正在考虑几种方法,但我不确定选择哪一种方法.
1)unique_ptr
void someFunction(std::unique_ptr<std::vector<string>> items) {
   // Also, make `m_itmes` std::unique_ptr<std::vector<string>>
   m_items = std::move(items);
}
2)按值传递并移动
void someFunction(std::vector<string> items) {
   m_items = std::move(items);
}
3)右值
void someFunction(std::vector<string>&& items) {
   m_items = std::move(items);
}
我应该避免哪种方法?为什么?
Ste*_*mer 32
除非你有理由让矢量生活在堆上,否则我建议不要使用 unique_ptr
无论如何,向量的内部存储都存在于堆上,因此如果使用unique_ptr,则需要2度间接,一个用于取消引用向量的指针,再次取消引用内部存储缓冲区.
因此,我建议使用2或3.
如果你选择选项3(需要一个右值引用),你就要求你的类用户在调用时传递一个右值(直接来自临时值,或从左值移动)someFunction.
从左值移动的要求是繁重的.
如果您的用户想要保留向量的副本,他们必须跳过箍来这样做.
std::vector<string> items = { "1", "2", "3" };
Test t;
std::vector<string> copy = items; // have to copy first
t.someFunction(std::move(items));
但是,如果你使用选项2,用户可以决定是否要保留副本 - 选择是他们的
保留一份副本:
std::vector<string> items = { "1", "2", "3" };
Test t;
t.someFunction(items); // pass items directly - we keep a copy
不要保留副本:
std::vector<string> items = { "1", "2", "3" };
Test t;
t.someFunction(std::move(items)); // move items - we don't keep a copy
Jus*_*tin 15
从表面上看,选项2似乎是一个好主意,因为它在单个函数中处理左值和右值.然而,正如Herb Sutter在他的CppCon 2014谈话回归基础知识中所说的那样!现代C++风格的要点,这是对左值的常见情况的悲观.
如果m_items"大于" items,则原始代码不会为向量分配内存:
// Original code:
void someFunction(const std::vector<string>& items) {
   // If m_items.capacity() >= items.capacity(),
   // there is no allocation.
   // Copying the strings may still require
   // allocations
   m_items = items;
}
复制赋值运算符on std::vector足够智能,可以重用现有的分配.另一方面,按值获取参数将始终需要进行另一次分配:
// Option 2:
// When passing in an lvalue, we always need to allocate memory and copy over
void someFunction(std::vector<string> items) {
   m_items = std::move(items);
}
简单地说:复制构造和复制分配不一定具有相同的成本.复制分配比复制构建更有效 - 它std::vector和std::string †更有效.
正如Herb所说,最简单的解决方案是添加一个右值超载(基本上是你的选项3):
// You can add `noexcept` here because there will be no allocation‡
void someFunction(std::vector<string>&& items) noexcept {
   m_items = std::move(items);
}
请注意,复制分配优化仅在m_items已存在时才有效,因此按值将构造函数的参数完全正确 - 分配必须以任一方式执行.
TL; DR:选择添加选项3.也就是说,对于左值有一个重载,对于右值有一个重载.选项2强制复制构造而不是复制分配,这可能更昂贵(并且用于std::string和std::vector)
†如果你想看到基准测试显示选项2可能是一个悲观,在谈话的这一点上,Herb显示了一些基准测试
‡我们不应该标明这是noexcept如果std::vector我们继续前进,赋值运算符是不是noexcept.如果您使用自定义分配器,请参阅文档.
根据经验,请注意,只有noexcept类型的移动赋值才能标记类似的函数noexcept
这取决于您的使用模式:
选项1
优点:
缺点:
unique_ptr,否则这不会提高可读性vector必须成为一个.由于标准库容器是使用内部分配来存储其值的托管对象,这意味着每个这样的向量将有两个动态分配.一个用于唯一ptr的管理块+ vector对象本身,另一个用于存储的项目.摘要:
如果您始终使用a管理此向量unique_ptr,请继续使用它,否则不要使用它.
选项2
优点:
此选项非常灵活,因为它允许调用者决定是否要保留副本:
std::vector<std::string> vec { ... };
Test t;
t.someFunction(vec); // vec stays a valid copy
t.someFunction(std::move(vec)); // vec is moved
当调用者使用std::move()该对象时,只移动两次(无副本),这是有效的.
缺点:
std::move(),总是调用复制构造函数来创建临时对象.如果我们要使用void someFunction(const std::vector<std::string> & items)并且我们m_items已经足够大(在容量方面)可以容纳items,那么分配m_items = items将只是一个复制操作,没有额外的分配.摘要:
如果你事先知道该对象将被重新运行时-set很多次,主叫方并不总是使用std::move(),我会避开它.否则,这是一个很好的选择,因为它非常灵活,尽管存在问题,但仍然可以根据需求提供用户友好性和更高性能.
选项3
缺点:
此选项强制呼叫者放弃他的副本.因此,如果他想为自己保留一份副本,他必须编写额外的代码:
std::vector<std::string> vec { ... };
Test t;
t.someFunction(std::vector<std::string>{vec});
摘要:
这比选项#2灵活性差,因此在大多数情况下我会说低劣.
选项4
考虑到选项2和3的缺点,我认为建议另外一个选择:
void someFunction(const std::vector<int>& items) {
    m_items = items;
}
// AND
void someFunction(std::vector<int>&& items) {
    m_items = std::move(items);
}
优点:
缺点:
摘要:
只要你没有这样的原型,这是一个很好的选择.
| 归档时间: | 
 | 
| 查看次数: | 2117 次 | 
| 最近记录: |