5

有多种方法可以从 C++ 中的类的方法返回项目集合。

例如,考虑侦听通过连接发送的所有消息的类 MessageSpy。客户端可以通过多种方式访问​​消息传递信息。

  1. const CollectionClass MessageSpy::getMessages()
  2. 迭代器 MessageSpy::begin()、迭代器 MessageSpy::end()
  3. 无效 MessageSpy::getMessages(OutputIterator)
  4. 无效 MessageSpy::eachMessage(Functor)
  5. 其他...

每种方法都有其权衡。例如:方法 1 需要复制整个集合,这对于大型集合来说是昂贵的。虽然方法 2 使该类看起来像一个不适合视图的集合...

由于我一直在纠结选择最合适的方法,我想知道您在考虑这些方法时会考虑哪些权衡/成本?

4

2 回答 2

7

如果您需要尽可能轻量级的解决方案,我建议使用基于迭代器/基于回调的方法。

原因是它将供应商与消费者的使用模式分离。

特别是,将结果放入集合1(即使结果可能“优化” - 可能进入 (N)RVO 或移动而不是复制对象)仍然会为全部容量分配一个完整的容器。

编辑:1对“必修论文”的极好补充(它们不是;如果您想了解事物,它们会非常有帮助):想要速度吗?通过戴夫亚伯拉罕的价值。

现在

  • 如果消费者在前几个元素之后实际上停止处理数据,这将是矫枉过正

    for(auto f=myType.begin(), l=myType.end(); f!=l; ++f)
    {
         if (!doProcessing(*f))
             break;
    }
    
  • 即使消费者最终处理所有元素,这也可能不是最理想的:可能不需要在任何特定时刻复制所有元素,因此可以重用“当前元素”的“插槽”,减少内存需求,增加缓存地方性。例如:

    for(auto f=myType.begin(), l=myType.end(); f!=l; ++f)
    {
         myElementType const& slot = *f; // making the temp explicit
         doProcessing(slot);
    }
    

请注意,如果消费者 确实想要一个包含所有元素的集合,迭代器接口仍然是优越的:

std::vector<myElementType> v(myType.begin(), myType.end());

// look: the client gets to _decide_ what container he wants!
std::set<myElementType, myComparer> s(myType.begin(), myType.end());

尝试以其他方式获得这种灵活性。

最后,还有一些风格元素:

  • 从本质上讲,使用迭代器很容易公开(const)对元素的引用;这使得避免对象切片和使客户端能够以多态方式使用元素变得更加容易。

  • 可以重载迭代器样式接口以在取消引用时返回非常量引用。要返回的容器,不能包含引用(直接)

  • 如果您遵守 C++11 中基于范围的要求,您可以使用一些语法糖:

    for (auto& slot : myType)
    {
         doProcessing(slot);
    }
    

最后,(如上所示),一般意义上的迭代器可以很好地与标准库一起工作。


回调样式(以及类似的输出迭代器样式)具有迭代器样式的许多优点(即,您可以使用返回值中途中止迭代,并且您可以在不预先分配所有元素的副本的情况下进行处理),但是在我看来,使用起来不太灵活。当然,在某些情况下,您可能希望鼓励一种特定的使用模式,这可能是一个不错的方法。

于 2013-08-02T20:06:30.333 回答
2

我会想到的第一件事(你不知何故根本没有提到)是

const CollectionClass& MessageSpy::getMessages()

注意&。这将返回您无法修改但可以自由接受的 const 引用。

没有抄袭,除非你真的想抄袭。

如果这不合适,例如,Qt对大量类使用“隐式数据共享” 。即你的类是“有点”按值返回的,但是它们的内部数据是共享的,直到你尝试对其中一个执行写操作。在这种情况下,您尝试写入的类执行深层复制,并且数据停止共享。这意味着移动的数据更少。

还有一些人对 SO 的返回值优化似乎太喜欢了。基本上,当您按值返回较大的值时,某些编译器在某些情况下可以消除额外的副本,并立即绕过额外的赋值传递值,这可能比按引用返回更快。我不会过分依赖它,但是如果您分析了您的代码并发现使用 RVO 提供了很好的加速,那么它是值得使用的。

我不会推荐“迭代器”,因为在没有auto关键字的 C++03 编译器上使用它们是#&@ 中的皇家痛苦。长名称或许多 typedef。我会改为返回对容器本身的 const 引用。

于 2013-08-02T21:00:57.510 回答