“使用std :: begin;”是一个好的做法吗?

6
据我所知,begin(some_vector)some_vector.begin()更标准,因为它支持数组...而且我也知道,使用using关键字并不是一种理想的行为。然而,我还看到很多代码只包含这两个using:
using std::begin;
using std::end;

这是否被视为良好还是不良的实践?特别是需要许多beginend时?

4个回答

14
据我所了解,begin(some_vector)some_vector.begin()更通用,因为它支持数组。
它不是“更通用”,两者都是100%标准的。它更加通用,因为它适用于数组,但是实际上很少有一个对象不知道它是数组还是容器类型。如果您有一个确定是数组的东西,请使用std :: begin(a),但是如果您有一个确定不是数组的东西,则使用同时适用于数组的形式没有优势。如果您在可能具有其中一种的通用上下文中,则std :: begin适用于两种情况。
使用using关键字并不是真正理想的行为。
这是有争议的。在大多数情况下(即using namespace foo),避免使用指示性的目录是非常好的理由,但是相同的论点不适用于引入单个名称的使用声明(即using foo :: bar)。例如,使用std :: swap的推荐方式是通过using std :: swap进行的,因此在一般情况下,using关键字并非总是不可取的。
特别是当需要许多begin和end时,这被认为是好的做法还是坏的做法?

我认为总的来说这是一个不好的想法。就像其他答案所解释的那样,它允许通过ADL找到beginend,但这不一定是一个好事情。启用beginend的ADL可能会引起问题,这就是为什么我们在最后一刻更改了基于范围的for循环,使其不使用using std :: begin; using std :: end;以避免歧义,请参见 N3257 获取详细信息。


使用基于范围的for循环时,要使用ADL来获取begin/end函数。如果一切都失败了,那就没有别的办法了,对吧? - T.C.
@T.C. 对于基于范围的 for,最后的手段实际上是一个奇怪的查找,在其他地方不存在(它 使用相关的命名空间,而不是作用域中的其他名称!)它不等同于 using std::begin; auto b = begin(range); 因此当且仅当 std 是关联命名空间时才会找到 std::begin(与 [swappable.requirements] 中指出的 std::swap 是重载决议的候选项不同)。std::begin 不必成为候选项,因为 std::begin(r) 仅在 r.begin() 有效时才起作用,如果这样有效,则基于范围的 for 的项目 (1.2) 将使用它。 - Jonathan Wakely
@T.C. 我已经修改了我的答案的最后一句话,以使其更准确地描述变化,因为你是正确的,某种形式的ADL仍可能发生。与此问题相关的部分是它不使用 using std::begin; using std::end;,因为那会导致问题。 - Jonathan Wakely
1
@IlyaPopov,最后,您还可以通过使用一个薄的适配器类将迭代功能添加到现有类中,该适配器类具有begin()end()成员,这些成员仅简单地转发到被适配的类的某些其他成员。因此,如果您有一个具有First()Last()成员的类,则可以执行for(auto i:make_rangelike(c)),其中该函数创建存储&c的适配器类,其begin()调用m_wrapped->First(),其end()调用m_wrapped->Last()。这可能比通过添加自由函数来适应类更安全,以便ADL找到它们。 - Jonathan Wakely
@JonathanWakely,感谢您的解释!我曾经使用过using std :: begin这个东西,但从现在开始,我可能更喜欢不这样做。 - Ilya Popov
显示剩余4条评论

7

它对于ADL的帮助可能会有所帮助。可以这样理解:

template<typename T, typename F>
void apply(T& v, const F& f)
{
   using std::begin;
   using std::end;
   std::for_each(begin(v), end(v), f);
}

因此,我们只需使用具有begin/end函数和经典C数组的类型调用apply即可。

在其他情况下,在源文件中,在小范围内使用using指令是可以的,但在头文件中使用是不好的。


+1非常好的技巧,但是我有点怀疑它的可读性,因为我应该为for_each、max_element等分别创建一个。 - Humam Helfawi
@HumamHelfawi for_each只是一个例子。而apply也只是一个例子而已。这样的ADL助手在swap函数中非常常见。 - ForEveR
这不仅没什么用,因为 std::beginstd::end 可能已经做了正确的事情,而且这会导致您的代码无法编译,因为您可能会发现例如 boost::beginstd::begin 存在二义性。 - Jonathan Wakely
swap是一个自定义点,旨在与using std::swap一起使用,并允许通过ADL找到其他定义。我不确定beginend应该以这种方式使用。 - Jonathan Wakely

5
这被认为是好的还是不好的实践呢?这取决于很多情境,我认为这个答案同样适用于通过using引入名称的更一般性问题。限制using的使用范围可以使代码更易读(例如函数级别范围);必要时使用,并谨慎使用。特别有趣的情况在于ADL。
template <class Container>
void func(Container& c)
{
  std::for_each(begin(c), end(c), /* some functor*/);
}

既然容器是由std命名空间创建的,那么就会使用ADL,找到std::beginstd::end函数。如果容器是由自定义的命名空间创建的,那么beginend函数可以在该命名空间中找到(在这个讨论中,我假设容器提供者已经提供了这些函数)。

如果容器是普通的C语言风格数组,则不会找到std::begin(T (&array)[N])形式,因为C语言风格数组不属于std命名空间。在此引入using std::begin允许代码用于数组和容器。

template <class Container>
void func(Container& c)
{
  using std::begin; // if c is an array, the function will still compile
  using std::end;
  std::for_each(begin(c), end(c), /* some functor*/);
}
// ...
int a[100];
func(a); // works as expected

演示


1
这要看情况而定。在头文件中使用声明(以及更多的使用指令)是不被鼓励的。但是,如果在函数体内您需要多次使用另一个命名空间中的一个或多个函数,那么使用声明/指令(放在函数体内,所以仅限于其范围)可以使代码更易读。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接