Um benefício std::begin
e std::end
é que eles servem como pontos de extensão para implementar a interface padrão para classes externas.
Se você deseja usar a CustomContainer
classe com função for loop ou template baseada em intervalo, que espera .begin()
e .end()
métodos, obviamente precisará implementar esses métodos.
Se a classe fornecer esses métodos, isso não será um problema. Quando isso não acontecer, você precisará modificá-lo *.
Isso nem sempre é viável, por exemplo, ao usar uma biblioteca externa, especialmente a comercial e a de código fechado.
Nessas situações, std::begin
e std::end
é útil, pois é possível fornecer API do iterador sem modificar a própria classe, mas sobrecarregando as funções livres.
Exemplo: suponha que você gostaria de implementar uma count_if
função que utiliza um contêiner em vez de um par de iteradores. Esse código pode ficar assim:
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));
}
Agora, para qualquer classe que você gostaria de usar com esse costume count_if
, você só precisa adicionar duas funções livres, em vez de modificar essas classes.
Agora, o C ++ tem um mecanismo chamado Argl Dependent Lookup
(ADL), que torna essa abordagem ainda mais flexível.
Em resumo, ADL significa que quando um compilador resolver uma função não qualificada (ou seja, função sem espaço para nome, como em begin
vez de std::begin
), ele também considerará funções declaradas nos espaços para nome dos seus argumentos. Por exemplo:
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);
Neste caso, não importa que nomes qualificados são some_lib::begin
e some_lib::end
- uma vez que CustomContainer
está em some_lib::
muito, compilador usará essas sobrecargas no count_if
.
Essa também é a razão de ter using std::begin;
e using std::end;
entrar count_if
. Isso nos permite usar não qualificado begin
e end
, portanto, permitindo ADL e
permitindo que o compilador escolha std::begin
e std::end
quando nenhuma outra alternativa for encontrada.
Podemos comer o cookie e ter o cookie - ou seja, ter uma maneira de fornecer uma implementação personalizada de begin
/ end
enquanto o compilador pode voltar aos padrões.
Algumas notas:
Pelo mesmo motivo, existem outras funções semelhantes: std::rbegin
/ rend
,
std::size
e std::data
.
Como outras respostas mencionam, as std::
versões possuem sobrecargas para matrizes nuas. Isso é útil, mas é simplesmente um caso especial do que descrevi acima.
Usar std::begin
e amigos é uma boa idéia ao escrever o código do modelo, porque isso os torna mais genéricos. Para não-modelo, você também pode usar métodos, quando aplicável.
PS Estou ciente de que este post tem quase 7 anos. Me deparei com isso porque queria responder uma pergunta marcada como duplicada e descobri que nenhuma resposta aqui menciona ADL.