在 C++ 中,从性能和数据完整性的角度来看,我有哪些选择来公开集合?
我的问题是我想将内部数据列表返回给调用者,但我不想生成副本。Thant 让我要么返回对列表的引用,要么返回指向列表的指针。但是,我并不热衷于让调用者更改数据,我只是想让它读取数据。
- 我必须在性能和数据完整性之间做出选择吗?
- 如果是这样,一般来说走一种方式更好还是特定于案例?
- 还有其他选择吗?
在 C++ 中,从性能和数据完整性的角度来看,我有哪些选择来公开集合?
我的问题是我想将内部数据列表返回给调用者,但我不想生成副本。Thant 让我要么返回对列表的引用,要么返回指向列表的指针。但是,我并不热衷于让调用者更改数据,我只是想让它读取数据。
很多时候,调用者想要访问只是为了遍历集合。从 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;
};
使用这种方法,您不会暴露任何有关您的内部结构的信息,即您甚至有一个集合。
如果您使用数组、向量等,RichQ 的答案是一种合理的技术。
如果您使用的集合不是由序数值索引的......或者认为您可能需要在不久的将来某个时候......那么您可能需要考虑公开自己的迭代器类型,并且关联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)
{
// ...
}
也许是这样的?
const std::vector<mydata>& getData()
{
return _myPrivateData;
}
这样做的好处是它非常、非常简单,并且与您使用 C++ 一样安全。您可以像 RobQ 建议的那样进行投射,但如果您不复制,则无法阻止某人这样做。在这里,您必须使用const_cast
,如果您正在寻找它很容易发现它。
或者,迭代器可能会得到几乎相同的东西,但它更复杂。在这里使用迭代器的唯一额外好处(我能想到的)是你可以有更好的封装。
只有当底层集合的内容不随时间变化时,使用 const 引用或共享指针才会有所帮助。
考虑你的设计。调用者真的需要查看内部数组吗?你能重组代码,让调用者告诉对象如何处理数组吗?例如,如果调用者打算搜索数组,所有者对象可以这样做吗?
您可以将对结果向量的引用传递给函数。在某些编译器上,可能会导致代码稍微快一些。
我建议首先尝试重新设计,然后使用干净的解决方案,然后优化性能(如有必要)。
@Shog9 和 @RichQ 的解决方案的一个优点是它们将客户端与集合实现分离。
如果您决定将您的收藏类型更改为其他类型,您的客户仍然可以工作。
您想要的是只读访问,而无需复制整个数据块。你有几个选择。
首先,您可以只返回一个对您的数据容器的 const 引用,就像上面建议的那样:
const std::vector<T>& getData() { return mData; }
这具有具体性的缺点:您无法在不更改类接口的情况下更改内部存储数据的方式。
其次,您可以返回指向实际数据的 const 指针:
const T* getDataAt(size_t index)
{
return &mData[index];
}
这更好一些,但也需要您提供 getNumItems 调用,并防止索引越界。此外,您的指针的 const-ness 很容易被抛弃,您的数据现在是可读写的。
另一种选择是提供一对迭代器,这有点复杂。这具有与指针相同的优点,并且不需要(必然)需要提供 getNumItems 调用,并且需要更多的工作来去除迭代器的 const-ness。
管理此问题的最简单方法可能是使用 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());
}
正如您在网站上看到的那样,这具有范围可组合、可过滤等优点。
使用 const 是一个合理的选择。您可能还希望查看 boost C++ 库的共享指针实现。它提供了指针的优点,即您可能需要将共享指针返回到引用不允许的“null”。
http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm
在您的情况下,您将使共享指针的类型为 const 以禁止写入。
如果您有一个std::list
普通的旧数据(.NET 将其称为“值类型”),那么返回对该列表的 const 引用就可以了(忽略诸如 之类的邪恶事物const_cast
)
如果您有 a std::list
of 指针(或boost::shared_ptr
's),那只会阻止您修改集合,而不是集合中的项目。我的 C++ 太生疏了,无法在这一点上告诉你答案:-(
以下两篇文章详细阐述了封装容器类所涉及的一些问题以及封装容器类的必要性。尽管它们没有提供完整的工作解决方案,但它们基本上导致了与 Shog9 给出的相同方法。
第 1 部分:封装和吸血鬼
第 2 部分(现在需要免费注册才能阅读此内容):
Kevlin Henney 的Train Wreck Spotting
我建议按照EnumChildWindows的方式使用回调。您必须找到一些方法来防止用户更改您的数据。也许使用const
指针/引用。
另一方面,您可以将每个元素的副本传递给回调函数,每次都覆盖该副本。(您不想生成整个集合的副本。我只是建议一次制作一个元素的副本。这不应该花费太多时间/内存)。
MyClass tmp;
for(int i = 0; i < n; i++){
tmp = elements[i];
callback(tmp);
}