STL算法:为什么没有额外的容器接口(除了迭代器对之外)
STL algorithms: Why no additional interface for containers (additional to iterator pairs)?
我想知道为什么STL不重载它们的算法函数,这样我就可以通过简单地提供一个容器来调用它们,而不是采用更繁琐的方式来传递begin + end迭代器。我当然理解为什么我们还想使用迭代器对来处理容器/数组的子序列,但是,几乎所有对这些方法的调用都使用了整个容器:
std::for_each(myVector.begin(), myVector.end(), doSomething);
我觉得直接写
更方便,可读性和可维护性std::for_each(myVector, doSomething);
是否有STL不提供这些重载的原因?[编辑:我不是说要用这个受限的接口来取代接口,而是要也提供一个基于容器的接口!]它们会带来歧义吗?我在想这样的事情:
template<typename _Container, typename _Funct>
inline _Funct for_each(_Container c, _Funct f) {
return for_each(begin(c), end(c), f);
}
我错过了什么吗?
它们确实为许多算法引入了歧义。很多<algorithm>
看起来像
template<class iterator>
void do_something(iterator, iterator);
template<class iterator, class funct>
void do_something(iterator, iterator, funct);
如果添加额外的重载
template<class container, class funct>
void do_something(container, funct);
编译器在计算do_something(x, y)
的含义时会遇到一些麻烦。*)
x
和y
是同一个type
,它将匹配iterator = type
和container = type, funct = type
c++ 11试图用能够识别容器和迭代器之间区别的"概念"来解决这个问题。然而,这些"概念"被证明太复杂而不能成为标准,所以这些重载也不能成为标准。
*)编译器对此不同意,Comeau编译器声称它有歧义,g++ 4.5和MSVC 10调用第一个函数
在评论中进行了长时间的讨论之后,这里有一个不像预期那样工作的例子——使用一个容器适配器,它也可以兼作谓词。
#include <iostream>
#include <vector>
template<class iterator>
void test(iterator, iterator)
{
std::cout << "test iteratorn";
}
template<class iterator, class predicate>
void test(iterator, iterator, predicate)
{
std::cout << "test iterator, predicaten";
}
template<class container, class predicate>
void test(const container& cont, predicate compare)
{
std::cout << "test container, predicaten";
test(cont.begin(), cont.end(), compare);
}
template<class container>
class adapter
{
public:
typedef typename container::iterator iterator;
adapter(container* cont) : cont(cont)
{ }
iterator begin() const
{ return cont->begin(); }
iterator end() const
{ return cont->end(); }
bool operator()(const iterator& one, const iterator& two)
{ return *one < *two; }
private:
container* cont;
};
int main()
{
std::vector<int> v;
adapter<std::vector<int>> a(&v);
test(a, a);
}
输出:测试迭代器
http://ideone.com/wps2tZ
不幸的是,这是一个更普遍的问题;也就是说,迭代器的设计是为了击败那些蹩脚的C api和java风格的"将算法作为每个单独容器的方法"解决方案。它们是第一代通用解决方案,经过反思,它们不如我们花了20年时间思考后获得的其他可能的通用解决方案好,这并不奇怪。
添加这些容器重载只是解决问题空间的一小部分;它甚至可能使事情在未来变得更糟。解决方案是range, c++希望尽快引入。
要理解这一点,我认为必须理解c++算法的哲学。让我们先问这个问题:
为什么c++算法被实现为自由函数而不是成员函数?
答案很简单:避免实现爆炸。假设您有M
容器和N
算法,如果将它们实现为容器的成员,那么将会有M*N
实现。这种方法有两个(相关的)问题:
- 首先,它没有利用代码重用。大多数实现将被重复。 第二,执行爆炸,这是上述的直接后果。
c++通过将它们实现为自由函数来解决这些问题,因此您只有N
实现。操作容器的每个算法都接受一对迭代器,这对迭代器定义了范围。如果你想重载容器,而不是对迭代器,那么标准必须为每个算法提供这样的重载,并且将会有2*N
实现,这几乎违背了c++最初将算法与容器分开的目的,并且这些函数中的一半不做任何另一半不能做的事情。
所以我不认为这是一个大问题。只是为了避免一个单个参数,为什么要实现N
更多的函数(这也对其使用施加了一些限制,例如您不能将指针传递给它)?然而,如果程序员想要在他们的实用程序中使用这些函数,他们可以在任何时候基于标准算法实现它们!
你评论道:
2*N的实现实际上只有N个实现。其他N个是内联重载,它们直接调用算法的"真实"版本,所以它们是头文件。提供容器重载并不会破坏将算法与容器分离的目的,因为(正如你在我的例子中看到的)它们可以使用模板来处理所有类型的容器。
基于这个逻辑,我们可以很好地为M*N
算法辩护。那么让它们也成为成员函数(并在内部调用自由函数)?我相信很多面向对象的人更喜欢
auto result = container.accumulate(val);
/
auto result = std::accumulate(container.begin(), container.end(), val);
这是Herb Sutter博客上的一个相关答案:为什么没有基于容器的算法?它展示了反例,就像Bo Persson在上面的回答中所做的那样。
有一个范围操作符库打算解决这个问题。冗长的内容被删减了好几次。
你的例子看起来像这样:
auto newVector = myVector * doSomething;
是的,doSomething
-没有括号。
熟悉shell的习惯用法(使用std算法):
auto t = vector<int>{3,2,1,4} | sort | unique;
应该指出的是,定义您自己的琐碎包装器来添加容器化版本是非常容易的。
例如:template<typename Container, typename Func>
Func for_each(Container& c, Func f) {
return std::for_each(c.begin(), c.end(), f);
}
现在你可以打你想打的电话了。没有歧义,因为您的包装器不在std名称空间中。可以定义带const Container&参数的重载。如果你想要调用c++ -11常量迭代器方法的版本(例如cbegin()),我认为你需要以不同的方式命名包装器。我使用for_each_const。
显然,正如其他用户提到的,这是一个棘手的问题,所以不幸的是,它已经很长时间了,在标准库中仍然没有解决方案。但是,已经有一些范围库可用,例如Boost:: range和Adobe源代码库中的一个,它们不仅提供了您在问题中描述的简单界面,而且还提供了一些更花哨的功能。
你的例子可以完美地使用Boost(我们在下面是using namespace boost::range::adaptors
):
boost::for_each(myVector, doSomething);
我们还可以快速轻松地切片myVector
:
boost::for_each(myVector | sliced(10, 20), doSomething)
我们甚至可以用另一个压缩myVector
,通过谓词进行过滤,并在单个简单语句中对结果对的每个其他元素进行采样(这要求您在doSomethingElse中解压缩boost::combined
产生的元组):
boost::for_each(
boost::combined(myVector, myOtherVector) | strided(2), doSomethingElse)
- 使用std::multimap迭代器创建std::list
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++中带有List类的迭代器Segfault
- 如何在c++迭代器类型中包装std::chrono
- 集合上的输出迭代器:assign和increment迭代器
- Boost Spirit,获取迭代器内部语义动作
- 对于set上的循环-获取next元素迭代器
- 迭代器 STL C++是模板、类还是接口?
- 类型不可知的抽象以使用相同的运行时接口处理正向和反向迭代器和范围?
- 如何在基类中实现子类迭代器的统一接口?
- 迭代器的标准接口::operator*返回的比T&和std::p air更多
- C++:将成员容器的迭代器接口转发到类接口
- C++11 迭代器接口
- 是否有一个标准的包装器类通过迭代器和reverse_iterator提供统一的接口
- 在C++中,是否可以实现一个迭代器接口,其中推进会使"current"元素无效?
- 树容器迭代器接口
- 并发无锁单链表 C++ : 并发会影响接口吗?迭代器是否仍然有意义
- 如何为集合提供带有迭代器的 const 接口
- 为遗留的c链表接口提供c++迭代器
- STL算法:为什么没有额外的容器接口(除了迭代器对之外)