当前位置: 首页 > 知识库问答 >
问题:

自定义容器是否应该具有自由开始/结束功能?

红明德
2023-03-14

在创建按通常规则(即适用于STL算法、适用于行为良好的泛型代码等)运行的自定义容器类时,在C 03中实现迭代器支持和成员开始/结束函数就足够了。

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

然而,在这一点上,我不得不问-是建议调用完全限定的begin()函数(即std::begin(c))还是依赖ADL并调用begin(c)?

ADL在这种特殊情况下似乎毫无用处——因为std::begin(c)如果可能的话委托给c.begin(),通常的ADL优势似乎不适用。如果每个人都开始依赖ADL,所有自定义容器都必须在其必要的命名空间中实现额外的开始()/结束()自由函数。然而,一些来源似乎暗示,推荐的方式(即https://svn.boost.org/trac/boost/ticket/6357)是对开始/结束的无限制调用。

那么什么是C 11方式?容器库作者应该为他们的类编写额外的开始/结束函数来支持不合格的开始/结束调用,而不使用命名空间std;或使用std::begin;吗?

共有1个答案

任宾鸿
2023-03-14

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

第一个替代方案在遗留命名空间中提供非成员start()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()

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

  • 必须将std::begin和std::end作为C样式数组的后备选项引入每个显式调用范围(模板头的潜在陷阱和一般麻烦)

第二种选择是通过提供非成员函数模板adl\u begin()和adl\u end()将先前解决方案的using声明封装到单独的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的建议相同

缺点:有点冗长

  • adl\u begin()/adl\u end()不如begin()/end()简洁

注意:不确定这是否真的比之前的方法有所改进。

一旦已经放弃了start()/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()

在容器自己的名称空间中通过非成员的ADL方法是惯用的C 11方法,尤其是对于需要在遗留类和类模板上进行改装的通用函数。这与用户提供非成员函数的习惯用法相同。

对于只使用Standard Containers或C风格数组的代码,std::begin()std::end()可以在任何地方调用,而无需引入usy-声明,从而牺牲更多冗长的调用。这种方法甚至可以改进,但它需要摆弄命名空间std(对于类类型)或就地源修改(对于类模板)。它可以做到,但不值得维护麻烦。

在非泛型代码中,如果所讨论的容器在编码时是已知的,那么人们甚至可以仅将ADL用于标准容器,并明确限定C样式数组的std::begin/std::end。它失去了一些调用一致性,但节省了使用声明的时间。

 类似资料:
  • 正如标题中所述,我想将react路由器道具和自定义道具导入到我的组件中。 我的路线看起来像这样: 在这里我得到这个错误:类型{}缺少以下属性从类型CompProps:名称,历史,位置,匹配 错误来自我的组件,如下所示: 我是TS的初学者,但我认为RouteComponentProps应该包含(名称、历史、位置)为什么TS会把错误推到我身上。将自定义道具和路由器道具传递给组件的最佳实践是什么?

  • 经过之前编章的介绍,我们知道在 Python 中,常见的容器类型有: dict, tuple, list, string。其中也提到过可容器和不可变容器的概念。其中 tuple, string 是不可变容器,dict, list 是可变容器。 可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。当然具体的介绍,可以看回之前的文章,有图文介绍。 那么这里先提出一个问题

  • 单击任何NavLink或Link后,它总是打开PageNotFound组件。

  • 我想按字母顺序对字段名列表进行排序,但是我需要在比较器的doCompare方法中包含一个条件,以便如果字段名是“pk”,则始终将其排序到列表的顶部。我所拥有的内容如下,但我不确定我是否采取了正确的方法,特别是reurn值为-1000。对此的任何建议都将不胜感激。

  • 问题内容: 我一直在寻找类似于Java TreeSet在实例化时接收自定义比较器的功能,因此我不需要使用对象的默认相等性(和哈希码)条件。 我能想到的最接近的方法是将我的对象包装在一个私有的自定义类中,但这似乎很麻烦:(最终在编程时成为一种重复出现的主题,所以我想知道是否已经有一些东西可供我们使用。也许在公共图书馆? 谢谢 问题答案: 不,您已经找到了应该使用的解决方案。 即使是,它是令人难以接受

  • 我正在努力裁剪javax。验证。ConstraintValidator和javax。验证。根据我的需要限制ValidatorContext。我从格式错误的请求正文收到的响应消息始终采用以下形状: <代码> 此消息也以500而不是400错误请求的形式返回。我无法获得工作到解决方案来执行以下操作: 仅包括<代码> 我有以下代码: 向上面的代码发送格式错误的有效负载将导致如下消息: 我希望能够收到以下信