如何正确返回unique_ptr的集合

时间:2015-03-25 06:33:26

标签: c++ c++11 vector unique-ptr

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

作为一个例子,我有一个带有对象集合的简单类,这些对象都创建一次,之后不会改变。

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
}

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

3 个答案:

答案 0 :(得分: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;
};

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

答案 1 :(得分: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;
};

然后实现Receiver接口:

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

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

Container container;
ReceiverImpl receiver;
container.enumerate(receiver);

请参阅live example

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

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

答案 2 :(得分:4)

如果使用提升,在这种情况下我更喜欢tranform_iterator而不是iterator_facade

为了获得更实用的风格,我将代码分为两部分:

  • 创建一个可以将两个指针作为范围保持的类(允许基于循环的范围)。
  • 创建一个函数,该函数将lambda作为转换步骤并返回一个范围(隐藏transform_iterator)。

因此,转换步骤在取消引用迭代器时执行。

代码示例

#include <boost/iterator/transform_iterator.hpp>
#include <memory>
#include <vector>
#include <algorithm>

#include <stdio.h>

using namespace boost;
using namespace std;

template <typename Iterator>
class Range {
    public:
        Range(Iterator begin, Iterator end) : b(begin), e(end) { }
        Range(const Range &r) = default;
        Range(Range &&r) = default;
        Range &operator=(const Range &r) = default;
        Range &operator=(Range &&r) = default;

        template <typename Container>
            Range(Container &c) : b(c.begin()), e(c.end()) { }

        Iterator begin() { return b;}
        Iterator begin() const { return b; }

        Iterator end() { return e;}
        Iterator end() const { return e; }

        Iterator b;
        Iterator e;
};


template <typename Container, typename TransformFunc>
Range<transform_iterator<TransformFunc, typename Container::iterator>>
transform(Container &c, TransformFunc f) {
    using namespace boost;
    using cont_it = typename Container::iterator;
    using iterator = transform_iterator<TransformFunc, cont_it>;

    iterator b = iterator(c.begin(), f), e = iterator(c.end(), f);
    Range<iterator> r(b,e);

    return r;
}

int main(int, char **) {
    vector<unique_ptr<int>> foo;

    for (int i = 0; i < 10; i++) {
        foo.push_back(unique_ptr<int>(new int(10)));
    }

    auto f = [](unique_ptr<int> &i) { return i.get(); };
    for (auto *i : transform(foo, f) ) {
        printf("%p ", i);
    }
    return 0;
}