自定义容器应该有免费的开始/结束function?

当创build一个自定义的容器类,按照通常的规则(即与STLalgorithm一起工作,使用行为良好的通用代码等)工作时,在C ++ 03中,实现迭代器支持和成员开始/结束函数就足够了。

C ++ 11引入了两个新的概念 – 基于范围的循环和std :: begin / end。 基于范围的for循环理解成员开始/结束函数,所以任何C ++ 03容器都支持基于范围的开箱即用。 对于algorithm推荐的方法(根据Herb Sutter编写的“现代C ++代码”)是使用std :: begin代替成员函数。

然而,在这一点上,我不得不问 – 推荐的方法来调用一个完全合格的begin()函数(即std :: begin(c))或依赖于ADL并调用begin(c)?

ADL在这种情况下似乎没用 – 因为如果可能,std :: begin(c)委托给c.begin(),通常的ADL好处似乎不适用。 如果每个人都开始依赖ADL,所有的定制容器都必须在必需的名称空间中实现额外的begin()/ end()自由函数。 然而,有几个消息来源似乎暗示了不合格的开始/结束呼叫是推荐的方式(即https://svn.boost.org/trac/boost/ticket/6357 )。

那么什么是C ++ 11的方式? 容器库作者应该为他们的类编写额外的开始/结束函数,以便在不使用名称空间std的情况下支持非限定的开始/结束调用; 或使用std :: begin;?

有几种方法,每种方法都有自己的优点和缺点。 以下三种方法进行成本效益分析。

ADL通过自定义非成员begin() / end()

第一种select是在legacy命名空间中提供非成员的begin()end()函数模板,以将所需的function改进到任何可以提供它的类或类模板上,但是却有例如错误的命名约定。 调用代码可以依靠ADL来查找这些新function。 示例代码(基于@Xeo的评论):

 // LegacyContainerBeginEnd.h namespace legacy { // retro-fitting begin() / end() interface on legacy // Container class template with incompatible names template<class C> auto begin(Container& c) -> decltype(c.legacy_begin()) { return c.legacy_begin(); } // similarly for begin() taking const&, cbegin(), end(), cend(), etc. } // namespace legacy // print.h template<class C> void print(C const& c) { // bring into scope to fall back on for types without their own namespace non-member begin()/end() using std::begin; using std::end; // works for Standard Containers, C-style arrays and legacy Containers std::copy(begin(c), end(c), std::ostream_iterator<decltype(*begin(c))>(std::cout, " ")); std::cout << "\n"; // alternative: also works for Standard Containers, C-style arrays and legacy Containers for (auto elem: c) std::cout << elem << " "; std::cout << "\n"; } 

优点 :完全一致的一致和简洁的调用约定

  • 适用于定义成员.begin().end()任何标准容器和用户types
  • 适用于C风格的数组
  • 可以对任何不具有成员.begin()end() 类模板 legacy::Container<T>进行改进(也适用于范围for循环 !),无需修改源代码

缺点 :需要在许多地方使用声明

  • std::beginstd::end需要被引入每个显式的调用范围,作为C风格数组的回退选项(模板头和潜在的麻烦潜在的缺陷)

ADL通过自定义非成员adl_begin()adl_end()

第二种方法是通过提供非成员函数模板adl_begin()adl_end() ,将先前解决scheme的使用声明封装到单独的adl名称空间中,然后可以通过ADLfind它们。 示例代码(基于@Yakk的评论):

 // LegacyContainerBeginEnd.h // as before... // ADLBeginEnd.h namespace adl { using std::begin; // <-- here, because otherwise decltype() will not find it template<class C> auto adl_begin(C && c) -> decltype(begin(std::forward<C>(c))) { // using std::begin; // in C++14 this might work because decltype() is no longer needed return begin(std::forward<C>(c)); // try to find non-member, fall back on std:: } // similary for cbegin(), end(), cend(), etc. } // namespace adl using adl::adl_begin; // will be visible in any compilation unit that includes this header // print.h # include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope template<class C> void print(C const& c) { // works for Standard Containers, C-style arrays and legacy Containers std::copy(adl_begin(c), adl_end(c), std::ostream_iterator<decltype(*adl_begin(c))>(std::cout, " ")); std::cout << "\n"; // alternative: also works for Standard Containers, C-style arrays and legacy Containers // does not need adl_begin() / adl_end(), but continues to work for (auto elem: c) std::cout << elem << " "; std::cout << "\n"; } 

优点 :完全一致的调用约定

  • 与@ Xeo的build议+相同的优点
  • 重复的使用声明已被封装(DRY)

缺点 :有点冗长

  • adl_begin() / adl_end()不像begin() / end()
  • 也许也不是那么习惯(虽然这是明确的)
  • 等待C ++ 14返回types的扣除,也会污染std::begin / std::end命名空间

注意 :不知道这是否真的改善了以前的做法。

显式限定std::begin()std::end()到处

为什么不回到std::begin() / std::end()的合格调用呢? 示例代码:

 // LegacyIntContainerBeginEnd.h namespace std { // retro-fitting begin() / end() interface on legacy IntContainer class // with incompatible names template<> auto begin(legacy::IntContainer& c) -> decltype(c.legacy_begin()) { return c.legacy_begin(); } // similary for begin() taking const&, cbegin(), end(), cend(), etc. } // namespace std // LegacyContainer.h namespace legacy { template<class T> class Container { public: // YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED auto begin() -> decltype(legacy_begin()) { return legacy_begin(); } auto end() -> decltype(legacy_end()) { return legacy_end(); } // rest of existing interface }; } // namespace legacy // print.h template<class C> void print(C const& c) { // works for Standard Containers, C-style arrays as well as // legacy::IntContainer and legacy::Container<T> std::copy(std::begin(c), std::end(c), std::ostream_iterator<decltype(*std::begin(c))>(std::cout, " ")); std::cout << "\n"; // alternative: also works for Standard Containers, C-style arrays and // legacy::IntContainer and legacy::Container<T> for (auto elem: c) std::cout << elem << " "; std::cout << "\n"; } 

优点 :一致的调用约定几乎一般地工作

  • 适用于定义成员.begin().end()任何标准容器和用户types
  • 适用于C风格的数组

缺点 :一些细节和改造不是通用和维护问题

  • std::begin() / std::end()begin() / end()有点冗长
  • 只能对任何没有成员.begin()end() (并且没有源代码!)的 LegacyContainer进行改进以适应工作(也适用于范围for循环 ! – namespace std成员函数模板begin()end()
  • 只能通过在LegacyContainer<T> (对于模板可用)的源代码内部直接添加成员函数begin() / end()来将其LegacyContainer<T>为类模板LegacyContainer<T>namespace std技巧在这里不起作用,因为函数模板不能是部分专用的。

使用什么?

在容器自己的命名空间中通过非成员begin() / end()方法的ADL方法是惯用的C ++ 11方法,尤其是对于需要在传统类和类模板上进行改进的generics函数。 这与用户提供非成员swap()函数的习惯是一样的。

对于只使用标准容器或C风格数组的代码,可以调用std::begin()std::end() ,而不用引入使用声明,代价是更多的详细调用。 这种方法甚至可以改进,但是它需要摆弄namespace std (对于类types)或就地源修饰(对于类模板)。 它可以做到,但不值得维护的麻烦。

在非generics代码中,在编码时所涉及的容器是已知的,甚至可以只依赖于标准容器的ADL,并且明确地将std::begin / std::end限定为C风格的数组。 它失去了一些调用一致性,但节省使用声明。