Una ventaja de std::begin
y std::end
es que sirven como puntos de extensión para implementar una interfaz estándar para clases externas.
Si desea utilizar la CustomContainer
clase con función de bucle o plantilla basada en rango que espera .begin()
y .end()
métodos, obviamente tendría que implementar esos métodos.
Si la clase proporciona esos métodos, eso no es un problema. Cuando no es así, deberías modificarlo *.
Esto no siempre es factible, por ejemplo, cuando se utiliza una biblioteca externa, especialmente comercial y de fuente cerrada.
En tales situaciones, std::begin
y std::end
resulta útil, ya que uno puede proporcionar API de iterador sin modificar la clase en sí, sino más bien sobrecargar funciones libres.
Ejemplo: suponga que desea implementar una count_if
función que tome un contenedor en lugar de un par de iteradores. Tal código podría verse así:
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));
}
Ahora, para cualquier clase que desee utilizar con esta costumbre count_if
, solo tiene que agregar dos funciones libres, en lugar de modificar esas clases.
Ahora, C ++ tiene un mecanismo llamado Búsqueda dependiente de argumentos
(ADL), que hace que este enfoque sea aún más flexible.
En resumen, ADL significa que cuando un compilador resuelve una función no calificada (es decir, una función sin espacio de nombres, como en begin
lugar de std::begin
), también considerará las funciones declaradas en los espacios de nombres de sus argumentos. Por ejemplo:
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);
En este caso, no importa que los nombres calificados lo estén some_lib::begin
y some_lib::end
, dado CustomContainer
que some_lib::
también lo está, el compilador usará esas sobrecargas count_if
.
Esa es también la razón para tener using std::begin;
y using std::end;
entrar count_if
. Esto nos permite usar no calificado begin
y end
, por lo tanto, permitir ADL y
permitir que el compilador elija std::begin
y std::end
cuando no se encuentren otras alternativas.
Podemos comer la cookie y tener la cookie, es decir, tener una manera de proporcionar una implementación personalizada de begin
/ end
mientras el compilador puede recurrir a las estándar.
Algunas notas:
Por la misma razón, hay otras funciones similares: std::rbegin
/ rend
,
std::size
y std::data
.
Como se menciona en otras respuestas, las std::
versiones tienen sobrecargas para matrices desnudas. Eso es útil, pero es simplemente un caso especial de lo que he descrito anteriormente.
Usar std::begin
y amigos es una idea particularmente buena al escribir código de plantilla, porque esto hace que esas plantillas sean más genéricas. Para los que no son plantillas, también podría usar métodos, cuando corresponda.
PD: Soy consciente de que esta publicación tiene casi 7 años. Lo encontré porque quería responder una pregunta que estaba marcada como un duplicado y descubrí que ninguna respuesta aquí menciona ADL.