用于公开集合的有效替代方案

时间:2008-09-04 20:22:19

标签: c++ performance collections data-integrity

在C ++中,从性能和数据完整性的角度来看,我有哪些替代方案来展示集合?

我的问题是我想将一个内部数据列表返回给调用者,但我不想生成副本。 Thant让我返回对列表的引用,或指向列表的指针。但是,我并不想让调用者更改数据,我只是想让它读取数据。

  • 我是否必须在性能和数据完整性之间做出选择?
  • 如果是这样,一般来说更好的是走一条路还是特别适合这种情况?
  • 还有其他选择吗?

10 个答案:

答案 0 :(得分:7)

很多时候调用者想要访问只是迭代集合。从Ruby的书中拿出一页,让迭代成为你班级的私人方面。

#include <algorithm>
#include <boost/function.hpp>

class Blah
{
  public:
     void for_each_data(const std::function<void(const mydata&)>& f) const
     {
         std::for_each(myPreciousData.begin(), myPreciousData.end(), f);
     }

  private:
     typedef std::vector<mydata> mydata_collection;
     mydata_collection  myPreciousData;
};

使用这种方法,您不会暴露任何关于内部的内容,即您甚至拥有集合。

答案 1 :(得分:5)

如果你正在使用数组,向量等,

RichQ's answer是一种合理的技术。

如果您正在使用未按序数值编制索引的集合...或者认为您可能需要在不久的将来某个时间点......那么您可能需要考虑公开您自己的迭代器类型以及相关的begin() / end()方法:

class Blah
{
public:
   typedef std::vector<mydata> mydata_collection;
   typedef myDataCollection::const_iterator mydata_const_iterator;

   // ...

   mydata_const_iterator data_begin() const 
      { return myPreciousData.begin(); }
   mydata_const_iterator data_end() const 
      { return myPreciousData.end(); }

private:
   mydata_collection  myPreciousData;
};

...然后您可以以正常方式使用:

Blah blah;
for (Blah::mydata_const_iterator itr = blah.data_begin();
   itr != blah.data_end();
   ++itr)
{
   // ...
}

答案 2 :(得分:3)

也许是这样的?

const std::vector<mydata>& getData()
{
  return _myPrivateData;
}

这里的好处是它非常,非常简单,并且像在C ++中一样安全。你可以像RobQ建议的那样投出这个,但如果你没有复制的话,那么你无法阻止某人这样做。在这里,您必须使用const_cast,如果您正在寻找它,这很容易被发现。

或者,迭代器可能会让你几乎一样,但它更复杂。在这里使用迭代器(我能想到的)唯一的额外好处是你可以有更好的封装。

答案 3 :(得分:2)

使用const引用或共享指针只有在底层集合的内容不随时间变化时才有用。

考虑你的设计。调用者是否真的需要查看内部数组?你可以重组代码,以便调用者告诉对象如何处理数组?例如,如果调用者打算搜索数组,那么所有者对象可以执行吗?

您可以将结果向量的引用传递给函数。在某些编译器上可能会导致代码速度略快。

我建议先尝试重新设计,然后再使用干净的解决方案,优化性能第三(如有必要)。

答案 4 :(得分:2)

@ Shog9和@ RichQ解决方案的一个优点是它们将客户端与集合实现分离。

如果您决定将收藏类型更改为其他类型,则您的客户仍然可以使用。

答案 5 :(得分:1)

您想要的是只读访问而不复制整个数据块。你有几个选择。

首先,你可以向你的数据容器返回一个const引用,如上所述:

const std::vector<T>& getData() { return mData; }

这具有具体的缺点:您无法在不更改类的界面的情况下更改内部存储数据的方式。

其次,您可以返回指向实际数据的常量指针:

const T* getDataAt(size_t index)
{
   return &mData[index];
}

这有点好,但还要求您提供getNumItems调用,并防止越界索引。此外,你的指针的常量很容易丢弃,你的数据现在是可读写的。

另一个选择是提供一对迭代器,这有点复杂。这与指针具有相同的优点,并且(不一定)需要提供getNumItems调用,并且还需要更多工作来去除其常量的迭代器。

管理此问题的最简单方法可能是使用Boost Range:

typedef vector<T>::const_iterator range_iterator_type;
boost::iterator_range< range_iterator_type >& getDataRange()
{
    return boost::iterator_range(mData.begin(), mData.end());
}

这具有范围可组合,可过滤等优点,正如您在website上看到的那样。

答案 6 :(得分:0)

使用const是一个合理的选择。 您可能还希望查看boost C ++库的共享指针实现。它提供了指针的优点,即您可能需要将共享指针返回到引用不允许的“null”。

http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

在您的情况下,您将使共享指针的类型为const以禁止写入。

答案 7 :(得分:0)

如果你有一个std::list普通的旧数据(.NET会称之为'值类型'),那么返回一个对这个列表的const引用就没问题了(忽略像const_cast这样的邪恶事物)

如果你有一个std::list指针(或boost::shared_ptr),那么这只会阻止你修改集合,而不是集合中的项。我的C ++太生疏了,无法在这一点上告诉你答案: - (

答案 8 :(得分:0)

以下两篇文章详细阐述了封装容器类所涉及的一些问题和需求。虽然它们没有提供完整的解决方案,但它们基本上导致了与Shog9相同的方法。

第1部分:Encapsulation and Vampires
第2部分(现在需要免费注册才能阅读本文):Train Wreck Spotting
 作者:Kevlin Henney

答案 9 :(得分:0)

我建议使用EnumChildWindows的回调。您必须找到一些方法来阻止用户更改您的数据。也许使用const指针/引用。

另一方面,您可以将每个元素的副本传递给回调函数,每次都覆盖副本。 (你不想生成整个集合的副本。我只建议一次复制一个元素。这不应该花费太多时间/内存。)

MyClass tmp;
for(int i = 0; i < n; i++){
    tmp = elements[i];
    callback(tmp);
}