Ein Vorteil von std::begin
und std::end
ist, dass sie als Erweiterungspunkte für die Implementierung einer Standardschnittstelle für externe Klassen dienen.
Wenn Sie eine CustomContainer
Klasse mit bereichsbasierter for-Schleife oder Vorlagenfunktion verwenden möchten, die .begin()
und erwartet.end()
Methoden verwendet, müssen Sie diese Methoden natürlich implementieren.
Wenn die Klasse diese Methoden bereitstellt, ist dies kein Problem. Wenn dies nicht der Fall ist, müssen Sie es ändern *.
Dies ist beispielsweise bei Verwendung einer externen Bibliothek, insbesondere einer kommerziellen und einer Closed-Source-Bibliothek, nicht immer möglich.
In solchen Situationen std::begin
undstd::end
nützlich, da man eine Iterator-API bereitstellen kann, ohne die Klasse selbst zu ändern, sondern freie Funktionen zu überladen.
Beispiel: Angenommen, Sie möchten eine count_if
Funktion implementieren , die einen Container anstelle eines Paares von Iteratoren verwendet. Ein solcher Code könnte folgendermaßen aussehen:
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));
}
Jetzt müssen Sie für jede Klasse, die Sie mit dieser benutzerdefinierten Klasse verwenden möchten count_if
, nur zwei kostenlose Funktionen hinzufügen, anstatt diese Klassen zu ändern.
Jetzt verfügt C ++ über einen Mechanismus namens Argument Dependent Lookup
(ADL), der diesen Ansatz noch flexibler macht.
Kurz gesagt bedeutet ADL, dass ein Compiler, wenn er eine nicht qualifizierte Funktion auflöst (dh eine Funktion ohne Namespace, wie begin
anstelle von std::begin
), auch Funktionen berücksichtigt, die in Namespaces seiner Argumente deklariert sind. Beispielsweise:
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);
In diesem Fall spielt es keine Rolle, ob qualifizierte Namen vorhanden sind, some_lib::begin
und some_lib::end
- da dies auch der Fall CustomContainer
ist some_lib::
, verwendet der Compiler diese Überladungen incount_if
.
Das ist auch der Grund für using std::begin;
und using std::end;
in count_if
. Dies ermöglicht es uns, unqualifiziert zu verwenden begin
und end
somit ADL
zuzulassen und dem Compiler die Auswahl zu ermöglichen, std::begin
und std::end
wenn keine anderen Alternativen gefunden werden.
Wir können den Cookie essen und den Cookie haben - dh eine Möglichkeit haben, eine benutzerdefinierte Implementierung von begin
/ bereitzustellen, end
während der Compiler auf Standard- Cookies zurückgreifen kann.
Einige Notizen:
Aus dem gleichen Grund gibt es andere ähnliche Funktionen: std::rbegin
/ rend
,
std::size
und std::data
.
Wie in anderen Antworten erwähnt, std::
weisen Versionen Überladungen für nackte Arrays auf. Das ist nützlich, aber es ist einfach ein Sonderfall dessen, was ich oben beschrieben habe.
Die Verwendung von std::begin
und Freunden ist besonders beim Schreiben von Vorlagencode eine gute Idee, da diese Vorlagen dadurch allgemeiner werden. Für Nicht-Vorlagen können Sie gegebenenfalls auch Methoden verwenden.
PS Mir ist bewusst, dass dieser Beitrag fast 7 Jahre alt ist. Ich bin darauf gestoßen, weil ich eine Frage beantworten wollte, die als Duplikat markiert war, und festgestellt habe, dass hier keine Antwort ADL erwähnt.