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

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

C ++ 11引入了两个新概念 - 基于范围的循环和std :: begin / end。 基于范围的for循环理解成员开始/结束函数,所以任何C ++ 03容器都支持基于范围的开箱即用。 对于算法,推荐的方式(根据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 :: begin;?


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

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

第一种选择是在legacy名称空间内提供非成员的begin()end()函数模板,以将所需的功能改进到任何可以提供它的类或类模板上,但却有例如错误的命名约定。 调用代码可以依靠ADL来查找这些新功能。 示例代码(基于@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()任何标准容器和用户类型
  • 适用于C风格的数组
  • 可以对任何不具有成员.begin()end() 类模板 legacy::Container<T>进行加装(对于范围for循环 !),无需修改源代码
  • 缺点 :需要在许多地方使用声明

  • std::beginstd::end需要被引入到每个显式调用范围中作为C样式数组的回退选项(模板头和潜在的麻烦潜在缺陷)
  • ADL通过自定义非成员adl_begin()adl_end()

    第二种方法是通过提供非成员函数模板adl_begin()adl_end()将先前解决方案的使用声明封装到单独的adl名称空间中,然后可以通过ADL找到它。 示例代码(基于@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的建议+相同的优点
  • 重复的使用声明已被封装(DRY)
  • 缺点 :有点冗长

  • adl_begin() / adl_end()不像begin() / end()那样简洁
  • 它可能也不是那么习惯(虽然它是明确的)
  • 等待C ++ 14返回类型扣除,也会污染std::begin / std::end命名空间
  • 注意 :不知道这是否真的改善了以前的做法。

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

    无论如何,一旦begin() / 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()任何标准容器和用户类型
  • 适用于C风格的数组
  • 缺点 :一些细节和改造不是通用和维护问题

  • std::begin() / std::end()begin() / end()有点冗长
  • 只能通过提供显式的非特定化来对任何没有成员.begin()end() (并且没有源代码!)的 LegacyContainer进行改进以适用于工作(也适用于范围for循环 ! - namespace std成员函数模板begin()end()
  • 只能通过在LegacyContainer<T> (对于模板可用)的源代码中直接添加成员函数begin() / end()来将其改进到类模板 LegacyContainer<T>namespace std技巧在这里不起作用,因为函数模板不能部分专用。
  • 要使用什么?

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

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

    在非泛型代码中,在编码时已知问题的容器,人们甚至可以仅依赖于标准容器的ADL,并明确限定std::begin / std::end为C风格的数组。 它失去了一些调用一致性,但节省了使用声明。

    链接地址: http://www.djcxy.com/p/47355.html

    上一篇: Should custom containers have free begin/end functions?

    下一篇: Boilerplate typedefs for STL