我应该使用指针还是移动语义来传递大块数据?

时间:2013-07-29 16:00:05

标签: c++ pointers c++11 move

我对推荐的编码技术有疑问。我有一个模型分析工具,我有时需要传递大量数据(从工厂类到拥有多个异构块的工厂类)。

我的问题是,是否应该使用指针或移动所有权是否存在一些共识(我需要尽可能避免复制,因为数据块的大小可能大到1 GB)。

指针版本如下所示:

class FactoryClass {
...
public:
   static Data * createData() {
      Data * data = new Data;
      ...
      return data;
   }
};

class StorageClass {
   unique_ptr<Data> data_ptr;
...
public:
   void setData(Data * _data_ptr) {
      data_ptr.reset(_data_ptr);
   }
};

void pass() {
   Data * data = FactoryClass::createData();
   ...
   StorageClass storage;
   storage.setData(data);
}

而移动版本是这样的:

class FactoryClass {
...
public:
   static Data createData() {
      Data data;
      ...
      return data;
   }
};

class StorageClass {
   Data data;
...
public:
   void setData(Data _data) {
      data = move(_data);
   }
};

void pass() {
   Data data = FactoryClass::createData();
   ...
   StorageClass storage;
   storage.setData(move(data));
}

我更喜欢移动版本 - 是的,我需要将移动命令添加到主代码中,但最后我只有存储中的对象,我不再需要关心指针语义。

然而,在使用我不详细了解的移动语义时,我并不是很放松。 (我不关心C ++ 11的要求,因为代码已经只有Gcc4.7 +可编译)。

有人会有一个支持这两个版本的引用吗?或者是否有其他一些首选数据如何传递数据?

由于关键字通常导致其他主题,我无法使用Google。

感谢。

编辑说明: 第二个例子被重构以包含评论中的建议,语义保持不变。

1 个答案:

答案 0 :(得分:11)

当您将对象传递给函数时,您传递的内容部分取决于该函数将如何使用它。函数可以通过以下三种方式之一使用对象:

  1. 它可以简单地在函数调用的持续时间内引用该对象,其中调用函数(或者它最终为调用堆栈的父元素)维护对象的所有权。在这种情况下,引用可以是常量引用或可修改引用。该函数不会长期存储此对象。

  2. 它可以直接复制对象。它没有获得原件的所有权,但它确实获得了原件的副本,以便存储,修改或复制它的内容。请注意,#1与此之间的区别在于复制在参数列表中显式化。例如,按值std::string取值。但这也可以像按int一样简单。

  3. 它可以获得对象的某种形式的所有权。然后函数对对象的破坏负有一定的责任。这也允许函数长期存储对象。

  4. 我对这些范例的参数类型的一般建议如下:

    1. 尽可能通过显式语言参考获取对象。如果那是不可能的,请尝试std::reference_wrapper。如果这不起作用,并且没有其他解决方案似乎合理,那么然后使用指针。一个指针可以用于像可选参数这样的东西(尽管C ++ 14的std :: optional会使它不那么有用。指针仍然会有用),语言数组(尽管如此,我们还有一些对象涵盖了这些参数的大部分用途) ),等等。

    2. 按值获取对象。那个人非常不容谈判。

    3. 通过value-move(即:将其移动到按值参数)或通过智能指针到对象(也将按值获取)来获取对象,因为无论如何你都要复制/移动它。您的代码的问题在于您通过指针转移所有权,但使用原始指针。原始指针没有所有权语义。在分配任何指针的那一刻,你应该立即将它包装在某种智能指针中。因此,您的工厂函数应该返回unique_ptr

    4. 您的案例似乎是#3。您在值移动和智能指针之间使用的完全取决于您。如果由于某种原因你必须堆分配Data,那么选择就是为你做的。如果Data可以进行堆栈分配,那么您有一些选择。

      我通常会根据对Data内部大小的估算来做到这一点。如果在内部,它只是几个指针/整数(和“少数”,我的意思是3-4),然后把它放在堆栈上就可以了。

      事实上,它可以更好,因为你将有更少的机会获得双重缓存。如果你的Data函数通常只是从另一个指针访问数据,如果你通过指针存储Data,那么它上面的每个函数调用都必须取消引用你存储的指针以获取内部指针,然后取消引用内部一。这是两个潜在的缓存未命中,因为两个指针都没有StorageClass的任何位置。

      如果按值存储DataData的内部指针很可能已经存在于缓存中。 StorageClass的其他成员有更好的地方性;如果您之前访问过某些StorageClass,那么您已经为缓存未命中付了代价,因此您可能已经在缓存中拥有Data

      但运动不是免费的。它比完整副本便宜,但它不是免费。您仍然在复制内部数据(并且可能会使原始数据上的任何指针无效)。但话说回来,在堆上分配内存也不是免费的。也没有解除它。

      但话又说回来,如果你不经常移动它(你移动它到达它的最终位置,但在那之后更多),即使移动一个更大的物体也没关系。如果你使用它比你移动它更多,那么对象存储的缓存局部性可能会胜过移动的成本。

      最终没有很多技术理由选择其中一个。我会说在合理的情况下默认移动。