如何正确返回unique_ptr的集合

bad*_*oms 17 c++ vector unique-ptr c++11

在更改我的代码以使用唯一指针后,我偶然发现了如何将一组对象返回给客户端.通常,我想将对象作为引用或非拥有指针传递.但是,如果我有一个对象集合,我不能只返回它的引用.

作为一个例子,我有一个带有对象集合的简单类,它们都创建了一次,之后没有改变.

using ObjectUPtr = std::unique_ptr<Object>;
class MyClass
{
  public:
  const std::vector<Object*>& GetObjectsOldStyle() const
  {
    return mObjectsOldStyle;
  }

  const std::vector<VObjectUPtr>& GetObjectsNewStyleA() const
  {
    // I don't like that: The client should not see the unique_ptr ...
    return mObjectsNewStyle; 
  }

  std::vector<VObject*> GetObjectsNewStyleB() const
  {
    // Ok, but performance drops
    std::transform(...); // Transform the collection and return a copy
  }

  const std::vector<VObject*>& GetObjectsNewStyleC() const
  {
    // Ok, only copied once, but two variables per collection needed
    // Transform the collection and cache in a second vector<Object*>
    std::transform(...);
  }

  std::vector<Object*> mObjectsOldStyle;    // old-style owning pointers here
  std::vector<ObjectUPtr> mObjectsNewStyle; // how I want to do it today
}
Run Code Online (Sandbox Code Playgroud)

今天,我通常更喜欢GetObjectsNewStyleB,但我想知道,如果有更优雅和有效的方式或一般的最佳实践如何返回这样的集合.

Ben*_*ley 12

我建议创建自己的迭代器类.然后创建开始和结束成员函数.你甚至可以重新设置dereference运算符来返回引用,而不是指针(除非你的指针可能为null).它可能会开始这样的事情:

class iterator :
    public std::iterator<std::random_access_iterator_tag, Object>
{
public:
    Object& operator*() const { return **base; }
    Object* operator->() const { return &**base; }
    iterator& operator++() { ++base; return *this; }

    // several other members necessary for random access iterators
private:
    std::vector<ObjectUPtr>::iterator base;
};
Run Code Online (Sandbox Code Playgroud)

实现一个标准的符合迭代器有点乏味,但我认为这是迄今为止最惯用的解决方案.正如评论中所提到的,Boost.Iterator库特别boost::iterator_facade可以用来缓解一些乏味.

  • 此外,通过使用`boost.iterator`来实现这一点,可以避免一些单调乏味. (2认同)

Ada*_*nek 6

您不必返回集合,从而打破容器类的封装.还有其他选择.

我会使用Enumerator/Receiver模式(我不知道这是否是这个模式的实际名称).

基本思想是让API的客户端实现一个接口,该接口原则上逐个接收来自容器的对象.

它看起来像这样:

class Receiver {
  public:
    virtual void receive(const Object& object) = 0;
};

class Container {
  public:
    void enumerate(Receiver& receiver) const {
      for (auto&& obj : m_objects) {
        receiver.receive(*obj);
      }
    }

  private:
    std::vector<ObjectUPtr> m_objects;
};
Run Code Online (Sandbox Code Playgroud)

然后实现Receiver接口:

class ReceiverImpl : public Receiver {
  public:
    virtual void receive(const Object& object) {
      // do something with object
    }  
};
Run Code Online (Sandbox Code Playgroud)

并让容器枚举接收器的对象:

Container container;
ReceiverImpl receiver;
container.enumerate(receiver);
Run Code Online (Sandbox Code Playgroud)

查看实例.

此外,您甚至可以通过添加互斥锁定/解锁来使容器线程安全Container::enumerate,客户端甚至不会注意到!

最后,您可以Container::enumerate使用模板参数替换receiver参数,以消除虚函数调用的运行时开销.