Одним из преимуществ std::begin
и std::end
является то, что они служат точками расширения для реализации стандартного интерфейса для внешних классов.
Если вы хотите использовать класс CustomContainer
с диапазоном дляФункция цикла или шаблона, которая ожидает методы .begin()
и .end()
, вам, очевидно, придется реализовать эти методы.
Если класс предоставляет эти методы, это не проблема.Если этого не произойдет, вам придется изменить его *.
Это не всегда возможно, например, при использовании внешней библиотеки, особенно коммерческой и закрытой.
В таких ситуациях std::begin
и std::end
пригодятся, поскольку можно предоставить API-интерфейс итератора, не изменяя сам класс, а скорее перегружая свободные функции.
Пример: предположим, что вы хотите реализовать функцию count_if
, которая принимает контейнер вместо пары итераторов.Такой код может выглядеть следующим образом:
template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
using std::begin;
using std::end;
return std::count_if(begin(container), end(container),
std::forward<PredicateType&&>(predicate));
}
Теперь для любого класса, который вы хотите использовать с этим пользовательским count_if
, вам нужно всего лишь добавить две свободные функции вместо изменения этих классов.
Теперь в C ++ есть механизм, называемый Argument Dependent Lookup (ADL), что делает такой подход еще более гибким.
Короче говоря, ADL означает, что при разрешении компиляторанеквалифицированная функция (то есть функция без пространства имен, например begin
вместо std::begin
), она также будет рассматривать функции, объявленные в пространствах имен своих аргументов.Например:
namesapce some_lib
{
// let's assume that CustomContainer stores elements sequentially,
// and has data() and size() methods, but not begin() and end() methods:
class CustomContainer
{
...
};
}
namespace some_lib
{
const Element* begin(const CustomContainer& c)
{
return c.data();
}
const Element* end(const CustomContainer& c)
{
return c.data() + c.size();
}
}
// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);
В этом случае не имеет значения, что квалифицированными именами являются some_lib::begin
и some_lib::end
- поскольку CustomContainer
также находится в some_lib::
, компилятор будет использовать эти перегрузки вcount_if
.
Это также причина наличия using std::begin;
и using std::end;
в count_if
.Это позволяет нам использовать неквалифицированные begin
и end
, что позволяет ADL и разрешать компилятору выбирать std::begin
и std::end
, когда других альтернатив не найдено.
Мыможет использовать cookie-файл и иметь cookie-файл, т. е. иметь способ предоставить пользовательскую реализацию begin
/ end
, в то время как компилятор может использовать стандартные.
Некоторые примечания:
По той же причине есть и другие похожие функции: std::rbegin
/ rend
, std::size
и std::data
.
Как упоминалось в других ответах,std::
версии имеют перегрузки для голых массивов.Это полезно, но это просто особый случай того, что я описал выше.
Использование std::begin
и друзей особенно хорошая идея при написании кода шаблона, потому что это делает эти шаблоны болееродовой.Для не шаблонов вы также можете использовать методы, когда это применимо.
PS Мне известно, что этому посту уже почти 7 лет.Я столкнулся с этим, потому что я хотел ответить на вопрос, который был отмечен как дубликат, и обнаружил, что ни один ответ здесь не упоминает ADL.