C++ 用于以通用方式返回序列的 API
如果我正在编写一个库并且我有一个需要返回值序列的函数,我可以执行以下操作:
std::vector<int> get_sequence();
但是,这需要库用户使用 std::vector<> 。 容器而不是允许他们使用他们想使用的任何容器。 此外,它还可以添加返回数组的额外副本(取决于编译器是否可以对此进行优化),这可能会对性能产生负面影响。
理论上,您可以通过创建一个带有开始迭代器和结束迭代器的模板化函数来启用任意容器的使用(并避免不必要的额外复制):
template<class T_iter> void get_sequence(T_iter begin, T_iter end);
然后,该函数将在迭代器给定的范围内存储序列值。 但这样做的问题是,它要求您知道序列的大小,以便在 begin
和 end
之间有足够的元素来存储序列中的所有值。
我想到了一个接口,例如:
template<T_insertIter> get_sequence(T_insertIter inserter);
它要求 T_insertIter 是一个插入迭代器(例如使用 std::back_inserter(my_vector)
创建),但这似乎太容易误用,因为编译器会很乐意接受非插入迭代器,但在运行时会表现不正确。
那么,是否有设计返回任意长度序列的通用接口的最佳实践呢?
If I am writing a library and I have a function that needs to return a sequence of values, I could do something like:
std::vector<int> get_sequence();
However, this requires the library user to use the std::vector<> container rather than allowing them to use whatever container they want to use. In addition, it can add an extra copy of the returned array (depending on whether the compiler could optimize this or not) that might have a negative impact on performance.
You could theoretically enable the use of arbitrary containers (and avoid the unnecessary extra copying) by making a templated function that takes a start and an end iter:
template<class T_iter> void get_sequence(T_iter begin, T_iter end);
The function would then store the sequence values in the range given by the iterators. But the problem with this is that it requires you to know the size of the sequence so you have enough elements between begin
and end
to store all of the values in the sequence.
I thought about an interface such as:
template<T_insertIter> get_sequence(T_insertIter inserter);
which requires that the T_insertIter be an insert iterator (e.g. created with std::back_inserter(my_vector)
), but this seems way too easy to misuse since the compiler would happily accept a non-insert iterator but would behave incorrectly at run-time.
So is there a best practice for designing generic interfaces that return sequences of arbitrary length?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
您可以做类似的事情
并要求提供的容器类型符合某些标准接口(例如具有成员push_back并且可能保留,以便您的接口的用户可以使用向量/双端队列/列表)。
You could do something like
and require that the supplied container type conforms to some standard interface (like having a member push_back and maybe reserve, so that the user of your interface can use vector/deque/list).
如果您已经为序列管理内存,则可以返回一对迭代器供调用者在 for 循环或算法调用中使用。
如果返回的序列需要管理自己的内存,那么事情会更加复杂。 您可以使用@paercebal 的解决方案,也可以实现自己的迭代器,将shared_ptr 保存到它们正在迭代的序列中。
If your already manage memory for your sequence, you can return a pair of iterators for the caller to use in for loop or an algorithm call.
If the returned sequence needs to manage its own memory, then things are more convoluted. You can use @paercebal's solution, or you can implement your own iterators that hold shared_ptr to the sequence they are iterating.
您可以使用 iterator_traits 静态分派迭代器的类型,
如下所示:
You can statically dispatch on the type of iterator using iterator_traits
Something like this :
为什么需要你的接口独立于容器? Scott Meyers 在他的“Effective STL”中给出了一个很好的理由:无论诱惑有多大,都不要尝试使代码独立于容器。 基本上,容器的用途完全不同:您可能不想将输出存储在映射或集合中(它们不是间隔容器),因此您只剩下向量、列表和双端队列,以及您为什么希望在需要列表的地方有向量,反之亦然? 它们完全不同,使用其中之一的所有功能会比尝试同时使用两者获得更好的结果。 好吧,考虑阅读“Effective STL”:它值得您花时间。
不过,如果您对容器有所了解,您可能会考虑做一些类似的事情
,或者
甚至可以通过策略来控制您所做的事情,例如
Why do you need your interface to be container-independent? Scott Meyers in his "Effective STL" gives a good reasoning for not trying to make your code container-independent, no matter how strong the temptation is. Basically, containers are intended for completely different usage: you probably don't want to store your output in map or set (they're not interval containers), so you're left with vector, list and deque, and why do you wish to have vector where you need list and vice versa? They're completely different, and you'll have better results using all the features of one of them than trying to make both work. Well, just consider reading "Effective STL": it's worth your time.
If you know something about your container, though, you may consider doing something like
or maybe
or even control what you do with a strategy, like
让 get_sequence 返回一个(自定义)
forward_iterator
类,该类可按需生成序列。 (如果对您的序列来说实用的话,它也可以是更高级的迭代器类型,如双向迭代器。)然后用户可以将序列复制到他们想要的任何容器类型中。 或者,它们可以直接在迭代器上循环并完全跳过容器。
您将需要某种结束迭代器。 如果不确切知道如何生成序列,则很难确切地说应该如何实现它。 一种方法是让迭代器类拥有一个返回结束迭代器的静态成员函数,例如:
其中
...
表示创建结束迭代器所需的任何参数(可能使用私有构造函数) )。 那么你的循环将如下所示:这是一个生成连续整数序列的前向迭代器类的简单示例。 显然,这可以很容易地变成双向或随机访问迭代器,但我想让示例保持较小的规模。
Have get_sequence return a (custom)
forward_iterator
class that generates the sequence on-demand. (It could also be a more advanced iterator type likebidirectional_iterator
if that's practical for your sequence.)Then the user can copy the sequence into whatever container type they want. Or, they can just loop directly on your iterator and skip the container entirely.
You will need some sort of end iterator. Without knowing exactly how you're generating the sequence, it's hard to say exactly how you should implement that. One way would be for your iterator class to have a static member function that returned an end iterator, like:
where
...
represents whatever parameters you need to create the end iterator (which might use a private constructor). Then your loop would look like:Here's a trivial example of a forward iterator class that generates a sequence of consecutive integers. Obviously, this could easily be turned into a bidirectional or random access iterator, but I wanted to keep the example small.
您可以将函子传递给接受单个值的函数。 然后函子将负责将值存储在您当时使用的任何容器中。
注意:我在这里使用 boost.function 来定义函子参数。 你不需要提升就能做到这一点。 它只是让它变得简单得多。
您还可以使用函数指针代替函子,但我不推荐它。 它很容易出错,并且没有简单的方法将数据绑定到它。
另外,如果您的编译器支持 C++0x lambda 函数,您可以通过消除显式函子定义来简化代码:(
我仍在使用 VS2008,所以我不确定 lambda 语法是否正确)
You could pass a functor to your function which accepts a single value. The functor would then be responsible for storing the value in whatever container you are using at the time.
Note: I'm using boost.function here to define the functor parameter. You don't need boost to be able to do this. It just makes it a lot simpler.
You could also use a function pointer instead of a functor, but I don't recommend it. It's error prone and there's no easy way to bind data to it.
Also, if your compiler supports C++0x lambda functions, you can simplify the code by eliminating the explicit functor definition:
(I'm still using VS2008 so I'm not sure if I got the lambda syntax right)
在我看来,
std::list
稍微好一些。 请注意,这不需要列表中数据的额外副本,因为它只是复制指针。这完全取决于你的消费者。 我说,如果您期望他们成为 C++ 开发人员,请为他们提供一个
std
容器类。我唯一想到的另一件事是你会这样做:
然后调用者可以使用 std::tr1::bind 使你的 get_sequence 函数调用任何函数他们想要的任何物体(或不想要的物体)。 您只需为您正在创建的每个元素不断调用
f
即可。std::list<int>
is slightly nicer, IMO. Note that this would not require an extra copy of the data in the list, as it's only pointers being copied.It depends entirely on your consumers. If you can expect them to be C++ developers, give them one of the
std
container classes, I say.The only other thing that occurs to me is that you'd do this:
Then the caller can use
std::tr1::bind
to make yourget_sequence
function call whatever function on whatever object (or not) that they want. You just keep callingf
for each element you're creating.对于输出序列,我看到两个选项。 第一个类似于
第二个
,您希望将其转换为标准 C++ 迭代器(为了方便起见,使用
boost::iterator_facade
)以在标准算法中使用它(copy,<代码>转换,...)。
还可以看看
boost::transform_iterator
,结合一些按顺序返回整数的迭代器。For outputting sequences, I see two options. The first is something like
The second is something like
that you would want to turn into a standard C++ iterator (using
boost::iterator_facade
for convenience) to use it in standard algorithms (copy
,transform
, ...).Have also a look at
boost::transform_iterator
, combined with some iterator returning integers in sequence.需要特别注意的一件事是,库是否指的是 DLL 或类似的东西。 如果库使用者(例如应用程序)是使用库本身之外的另一个编译器构建的,则可能会出现问题。
考虑您在示例中按值返回
std::vector
的情况。 然后,内存将在库的上下文中分配,但在应用程序的上下文中释放。 两个不同的编译器可能会以不同的方式分配/解除分配,因此可能会发生严重破坏。One thing to pay extra attention to is if you by library mean a DLL or similar. Then there might be problems if the library consumer, say an application, is built with another compiler than the library itself.
Consider the case where you in your example return a
std::vector<>
by value. Memory will then be allocated in the context of the library, but deallocated in the context of the application. Two different compilers might allocate/deallocate differently and so havoc might occur.呃...只是我的两分钱,但是:
这将消除潜在的复制回报问题。
现在,如果您确实想避免强加容器,您可以尝试类似的操作:
这将仅针对向量、列表和双端队列(以及类似的容器)进行编译。 如果您想要大量可能的容器,代码将是:
但正如其他帖子所说,您应该接受将您的接口限制为仅一种容器。
Err... Just my two cents, but:
This would remove the potential return by copy problem.
Now, if you really want to avoid imposing a container, you could try something like:
This would compile only for vectors, lists and deque (and similar containers). Should you want a larget set of possible containers, the code would be:
But as said by other posts, you should accept to limit your interface to one kind of containers only.