公开馆藏的有效替代方法
在C ++中,从性能和数据完整性的角度来看,对于公开一个集合我有什么选择?
我的问题是我想将内部数据列表返回给调用者,但是我不想生成副本。 Thant离开了我,或者返回对列表的引用,或者返回指向列表的指针。但是,我对让调用者更改数据并不感到疯狂,我只想让它读取数据。
- 我必须在性能和数据完整性之间进行选择吗?
- 如果是这样,通常是更好的一种方法还是对这种情况特别?
- 还有其他选择吗?
解决方案
回答
也许是这样的吗?
const std::vector<mydata>& getData() { return _myPrivateData; }
这样做的好处是,它非常,非常简单,并且与使用C ++一样安全。我们可以像RobQ所建议的那样进行转换,但是如果我们不进行复制,那么我们将无法阻止他人这样做。在这里,我们将不得不使用const_cast,如果我们正在寻找它,这很容易发现。
另外,迭代器可能会使我们获得几乎相同的东西,但是更加复杂。在这里(我能想到)使用迭代器的唯一添加好处是我们可以拥有更好的封装。
回答
使用const是一个合理的选择。
我们可能还希望检查boost C ++库的共享指针实现。它提供了指针的优点,即我们可能需要将共享指针返回为引用不允许的"空"。
http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm
在情况下,应将共享指针的类型设为const以禁止写入。
回答
如果我们使用的是数组,向量等,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) { // ... }
回答
如果我们有普通旧数据的" std :: list"(。NET将其称为"值类型"),则返回对该列表的const引用就可以了(忽略诸如const_cast之类的邪恶事物)
如果我们有一个指针" std :: list"(或者" boost :: shared_ptr"),那么这只会停止我们修改集合,而不会停止修改集合中的项目。我的C ++太生锈了,无法在此时告诉我们答案:-(
回答
仅当基础集合的内容不会随时间变化时,使用const引用或者共享指针才有帮助。
考虑设计。调用者真的需要查看内部数组吗?我们可以重组代码,以便调用程序告诉对象如何处理数组吗?例如,如果调用者打算搜索数组,所有者对象可以这样做吗?
我们可以将对结果向量的引用传递给函数。在某些编译器上,这可能会导致略微加快代码的速度。
我建议先尝试重新设计,其次是干净的解决方案,然后再进行性能优化(如有必要)。
回答
我们想要的是只读访问权限,而不复制整个数据块。我们有几种选择。
首先,我们可以将const引用返回到任何数据容器,如上面的建议:
const std::vector<T>& getData() { return mData; }
这具有具体性的缺点:在不更改类接口的情况下,我们无法更改内部存储数据的方式。
其次,我们可以将const-ed指针返回到实际数据:
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()); }
正如我们在网站上看到的那样,这具有范围可组合,可过滤等优点。
回答
@ Shog9和@RichQ解决方案的一个优点是,它们使客户端与集合实现脱钩。
如果我们决定将收藏夹类型更改为其他类型,客户仍然可以使用。
回答
很多时候,调用者只想访问就可以遍历集合。从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; };
使用这种方法,我们不会暴露任何有关内部的东西,即我们甚至没有收藏。
回答
以下两篇文章详细介绍了封装容器类所涉及的一些问题以及对封装类的需求。尽管它们没有提供完整的可行解决方案,但它们实际上导致了与Shog9相同的方法。
第1部分:封装和吸血鬼
第2部分(阅读此书现在需要免费注册):火车残骸发现
凯夫琳·亨尼(Kevlin Henney)
回答
我建议沿EnumChildWindows使用回调。我们将必须找到一些方法来防止用户更改数据。也许使用const指针/引用。
另一方面,我们可以将每个元素的副本传递给回调函数,以覆盖每次的副本。 (我们不想生成整个集合的副本。我只建议一次将一个元素复制一份。这不需要花费很多时间/内存)。
MyClass tmp; for(int i = 0; i < n; i++){ tmp = elements[i]; callback(tmp); }