Jedną z zalet std::begin
i std::end
jest to, że służą one jako punkty rozszerzeń dla realizacji standardowy interfejs dla klas zewnętrznych.
Jeśli chcesz użyć CustomContainer
klasy z funkcją pętli lub funkcji szablonu opartą na zakresie, która oczekuje .begin()
i .end()
metodami, oczywiście musisz zaimplementować te metody.
Jeśli klasa udostępnia te metody, nie stanowi to problemu. Jeśli nie, musisz go zmodyfikować *.
Nie zawsze jest to możliwe, na przykład w przypadku korzystania z biblioteki zewnętrznej, szczególnie komercyjnej i zamkniętej.
W takich sytuacjach std::begin
i std::end
przydają się, ponieważ można zapewnić interfejs API iteratora bez modyfikowania samej klasy, ale raczej przeciążając wolne funkcje.
Przykład: załóżmy, że chcesz zaimplementować count_if
funkcję, która pobiera kontener zamiast pary iteratorów. Taki kod może wyglądać następująco:
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));
}
Teraz dla każdej klasy, której chcesz używać z tym niestandardem count_if
, musisz tylko dodać dwie bezpłatne funkcje, zamiast modyfikować te klasy.
Teraz C ++ ma mechanisim o nazwie Argument Dependent Lookup
(ADL), co czyni takie podejście jeszcze bardziej elastycznym.
W skrócie, ADL oznacza, że gdy kompilator rozpozna funkcję niekwalifikowaną (tj. Funkcję bez przestrzeni nazw, jak begin
zamiast zamiast std::begin
), to również rozważy funkcje zadeklarowane w przestrzeni nazw swoich argumentów. Na przykład:
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);
W tym przypadku nie ma znaczenia, że są to kwalifikowane nazwy some_lib::begin
i some_lib::end
- ponieważ CustomContainer
jest some_lib::
również w, kompilator użyje tych przeciążeń w count_if
.
To również powód, dla posiadania using std::begin;
i using std::end;
w count_if
. To pozwala nam korzystać z niewykwalifikowanych, begin
a end
tym samym pozwalając na ADL i
pozwalając kompilatorowi wybierać std::begin
i std::end
kiedy nie ma innych alternatyw.
Możemy zjeść ciasteczko i mieć ciasteczko - tj. Mieć sposób na zapewnienie niestandardowej implementacji begin
/ end
podczas gdy kompilator może wrócić do standardowych.
Niektóre uwagi:
Z tego samego powodu, istnieją inne podobne funkcje: std::rbegin
/ rend
,
std::size
a std::data
.
Jak wspominają inne odpowiedzi, std::
wersje mają przeciążenia dla nagich tablic. Jest to przydatne, ale jest po prostu szczególnym przypadkiem tego, co opisałem powyżej.
Korzystanie std::begin
ze znajomych i przyjaciół jest szczególnie dobrym pomysłem podczas pisania kodu szablonu, ponieważ dzięki temu szablony są bardziej ogólne. W przypadku innych niż szablon równie dobrze możesz użyć metod, jeśli mają zastosowanie.
PS Wiem, że ten post ma prawie 7 lat. Natknąłem się na to, ponieważ chciałem odpowiedzieć na pytanie oznaczone jako duplikat i odkryłem, że w żadnej odpowiedzi nie ma mowy o ADL.