Одна користь 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
коли інших альтернатив не знайдено.
Ми можемо з’їсти печиво та мати печиво - тобто мати спосіб забезпечити власну реалізацію begin
/ в end
той час як компілятор може повернутися до стандартних.
Деякі примітки:
З цієї ж причини існують і інші подібні функції: std::rbegin
/ rend
,
std::size
і std::data
.
Як згадується в інших відповідях, std::
версії мають перевантаження для голих масивів. Це корисно, але це просто окремий випадок того, що я описав вище.
Використання std::begin
та друзів є особливо хорошою ідеєю при написанні коду шаблону, оскільки це робить ці шаблони більш загальними. Для інших шаблонів ви можете також добре використовувати методи, коли це застосовується.
PS Я знаю, що цій посаді майже 7 років. Я натрапив на це, тому що хотів відповісти на запитання, яке було позначене як дублікат, і виявив, що жодна відповідь тут не згадує ADL.