Un avantage de std::begin
etstd::end
est qu'ils servent de points d'extension pour implémenter une interface standard pour les classes externes.
Si vous souhaitez utiliser une CustomContainer
classe avec une fonction de boucle ou de modèle basée sur une plage qui attend .begin()
et des .end()
méthodes, vous devrez évidemment implémenter ces méthodes.
Si la classe fournit ces méthodes, ce n'est pas un problème. Dans le cas contraire, vous devrez le modifier *.
Cela n'est pas toujours possible, par exemple lors de l'utilisation d'une bibliothèque externe, en particulier une source commerciale et fermée.
Dans de telles situations, std::begin
et std::end
utile, car on peut fournir une API d'itérateur sans modifier la classe elle-même, mais plutôt surcharger les fonctions libres.
Exemple: supposons que vous souhaitiez implémenter une count_if
fonction qui prend un conteneur au lieu d'une paire d'itérateurs. Un tel code pourrait ressembler à ceci:
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));
}
Maintenant, pour n'importe quelle classe que vous souhaitez utiliser avec cette coutume count_if
, vous n'avez qu'à ajouter deux fonctions gratuites, au lieu de modifier ces classes.
Maintenant, C ++ a un mécanisme appelé
ADL ( Argument Dependent Lookup ), qui rend cette approche encore plus flexible.
En bref, ADL signifie que lorsqu'un compilateur résout une fonction non qualifiée (c'est-à-dire une fonction sans espace de noms, comme begin
au lieu de std::begin
), il considérera également les fonctions déclarées dans les espaces de noms de ses arguments. Par exemple:
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);
Dans ce cas, peu importe que les noms qualifiés soient some_lib::begin
et some_lib::end
- puisque CustomContainer
c'est some_lib::
aussi le cas, le compilateur utilisera ces surcharges danscount_if
.
C'est aussi la raison d'avoir using std::begin;
et using std::end;
de participer count_if
. Cela nous permet d'utiliser sans réserve begin
et end
, par conséquent, de permettre ADL et de
permettre au compilateur de choisir std::begin
et std::end
quand aucune autre alternative n'est trouvée.
Nous pouvons manger le cookie et l'avoir - c'est-à-dire avoir un moyen de fournir une implémentation personnalisée de begin
/end
tandis que le compilateur peut revenir à ceux standard.
Quelques notes:
Pour la même raison, il existe d'autres fonctions similaires: std::rbegin
/ rend
,
std::size
et std::data
.
Comme d'autres réponses le mentionnent, les std::
versions ont des surcharges pour les tableaux nus. C'est utile, mais c'est simplement un cas particulier de ce que j'ai décrit ci-dessus.
L'utilisation de std::begin
et d'amis est particulièrement bonne lors de l'écriture de code de modèle, car cela rend ces modèles plus génériques. Pour les non-modèles, vous pourriez tout aussi bien utiliser des méthodes, le cas échéant.
PS Je sais que ce post a presque 7 ans. Je suis tombé dessus parce que je voulais répondre à une question marquée comme doublon et j'ai découvert qu'aucune réponse ne mentionne ADL.