如何正确 return collection of unique_ptr
How to properly return a collection of unique_ptr
在更改我的代码以使用唯一指针后,我偶然发现了如何 return objects 的 collection 给客户端。通常,我想将 objects 作为引用或 non-owning 指针传递。但是,如果我有 objects 的 collection,我不能只 return 引用它。
举个例子,我有一个简单的 class,collection 为 objects,它们都是创建一次,之后就不会更改。
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,但我想知道,是否有更优雅、更有效的方法或一般最佳实践来说明如何 return 这样的 collection。
我建议创建您自己的迭代器 class。然后创建开始和结束成员函数。您甚至可以将解引用运算符重载为 return 引用,而不是指针(除非您的指针可能为空)。它可能像这样开始:
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
可以用来缓解一些单调乏味的事情。
您不必return 集合,从而打破容器的封装class。还有其他选择。
我会选择 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
中添加互斥体 lock/unlock 来使容器线程安全,而客户端甚至不会注意到!
最后,您可以将 Container::enumerate
中的接收者参数替换为模板参数,以消除虚函数调用的 运行 时间开销。
如果使用 boost,在这种情况下我更喜欢 tranform_iterator
而不是 iterator_facade
。
为了获得更实用的风格,我将代码分为两部分:
- 创建一个 class 可以将两个指针作为一个范围(允许基于范围的 for 循环)。
- 创建一个将 lambda 作为转换步骤和 returns 范围(隐藏
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;
}
在更改我的代码以使用唯一指针后,我偶然发现了如何 return objects 的 collection 给客户端。通常,我想将 objects 作为引用或 non-owning 指针传递。但是,如果我有 objects 的 collection,我不能只 return 引用它。
举个例子,我有一个简单的 class,collection 为 objects,它们都是创建一次,之后就不会更改。
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,但我想知道,是否有更优雅、更有效的方法或一般最佳实践来说明如何 return 这样的 collection。
我建议创建您自己的迭代器 class。然后创建开始和结束成员函数。您甚至可以将解引用运算符重载为 return 引用,而不是指针(除非您的指针可能为空)。它可能像这样开始:
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
可以用来缓解一些单调乏味的事情。
您不必return 集合,从而打破容器的封装class。还有其他选择。
我会选择 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
中添加互斥体 lock/unlock 来使容器线程安全,而客户端甚至不会注意到!
最后,您可以将 Container::enumerate
中的接收者参数替换为模板参数,以消除虚函数调用的 运行 时间开销。
如果使用 boost,在这种情况下我更喜欢 tranform_iterator
而不是 iterator_facade
。
为了获得更实用的风格,我将代码分为两部分:
- 创建一个 class 可以将两个指针作为一个范围(允许基于范围的 for 循环)。
- 创建一个将 lambda 作为转换步骤和 returns 范围(隐藏
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;
}