返回集合时的权衡

时间:2013-08-02 19:11:40

标签: c++

在C ++中,有多种方法可以从类的方法返回一组项目。

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

  1. const CollectionClass MessageSpy :: getMessages()
  2. Iterator MessageSpy :: begin(),Iterator MessageSpy :: end()
  3. void MessageSpy :: getMessages(OutputIterator)
  4. void MessageSpy :: eachMessage(Functor)
  5. 其他...
  6. 每种方法都有其权衡取舍。例如:方法1需要复制整个集合,这对于大型集合来说是昂贵的。虽然方法2使得类看起来像一个不适合视图的集合......

    由于我总是在选择最合适的方法,我想知道在考虑这些方法时你会考虑什么权衡/成本?

2 个答案:

答案 0 :(得分:7)

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

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

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

  
    

编辑: 1 是“强制性论文”的一个很好的补充(他们不是;如果你想了解事情,他们只是非常有帮助): Want Speed? Pass By value

  

现在

  • 如果使用者实际上在前几个元素之后停止处理数据,那就太过分了

    for(auto f=myType.begin(), l=myType.end(); f!=l; ++f)
    {
         if (!doProcessing(*f))
             break;
    }
    
  • 这可能是次优的,即使消费者最终处理其中的元素:可能不需要在任何特定时刻复制所有元素,因此'插槽'为' current element'可以重用,减少内存需求,增加缓存局部性。 E.g:

    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)对元素的引用;这使得更容易避免对象切片并使客户端能够使用多态元素。

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

  • 如果你坚持C ++ 11中基于范围的for的要求,你可以有一些语法糖:

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

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


回调样式(类似于Output-iterator样式)具有迭代器样式的许多好处(也就是说,您可以使用返回值来中途迭代迭代,并且可以在没有处理的情况下进行处理在前面分配所有元素的副本),但在我看来,使用中的灵活性稍差。当然,在某些情况下,您可能希望鼓励使用特定的使用模式,这是一个很好的方法。

答案 1 :(得分:2)

第一个的事情(你不知怎的根本没有提到)我会想到的是

const CollectionClass& MessageSpy::getMessages()

请注意&amp; 。这将返回const引用,该引用无法修改但可以自由接受。

不要复制,除非你真的想复制。

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

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

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