Czy niestandardowe kontenery powinny mieć darmowe funkcje begin/end?
Podczas tworzenia niestandardowej klasy kontenera, która działa według zwykłych reguł (tzn. działa z algorytmami STL, działa z dobrze zachowanym kodem generycznym, itp.), w C++03 wystarczyło zaimplementować obsługę iteratora oraz funkcje begin/end.
C++11 wprowadza dwie nowe koncepcje-range-based for loop oraz std:: begin / end. Zakres-based for loop rozumie funkcje rozpoczynające/kończące członków, więc wszelkie kontenery C++03 obsługują zakres-based for out of the box. Dla algorytmów zalecany sposób (zgodnie z "Writing modern C++ code" autorstwa Herba Suttera) jest użycie std:: begin zamiast funkcji member.
Jednak w tym momencie muszę zapytać - czy zalecanym sposobem jest wywołanie w pełni kwalifikowanej funkcji begin () (tj. std::begin (c)) lub poleganie na ADL i wywołanie begin (c)?
ADL wydaje się bezużyteczny w tym konkretnym przypadku - ponieważ STD::begin(c) deleguje do C.begin() jeśli to możliwe, zwykłe korzyści z ADL nie wydają się mieć zastosowania. A jeśli wszyscy zaczną polegać na ADL, wszystkie niestandardowe kontenery mają aby zaimplementować dodatkowe funkcje begin () / end () w wymaganych przestrzeniach nazw. Jednak niektóre źródła wydają się sugerować, że niewykwalifikowane wywołania do rozpoczęcia/zakończenia są zalecanym sposobem (np. https://svn.boost.org/trac/boost/ticket/6357).
Więc co to jest sposób C++11? Czy autorzy bibliotek kontenerów powinni pisać dodatkowe funkcje begin/end dla swoich klas, aby obsługiwać niekwalifikowane wywołania begin / end w przypadku braku użycia przestrzeni nazw std; lub użycia STD::begin;?
1 answers
Istnieje kilka podejść, każdy z własnymi zaletami i wadami. Poniżej trzy podejścia z analizą kosztów i korzyści.
ADL przez Niestandardowy nieczłonek begin()
/ end()
Pierwsza alternatywa dostarcza szablony funkcji spoza begin()
i end()
wewnątrz przestrzeni nazw, aby zmodernizować wymaganą funkcjonalność do dowolnej klasy lub szablonu klasy, który może ją dostarczyć, ale ma np. złą konwencję nazewnictwa. Kod wywołujący może wtedy polegać na ADL, aby znaleźć te nowe funkcje. Przykładowy kod (na podstawie komentarzy @Xeo):
// LegacyContainerBeginEnd.h
namespace legacy {
// retro-fitting begin() / end() interface on legacy
// Container class template with incompatible names
template<class C>
auto begin(Container& c) -> decltype(c.legacy_begin())
{
return c.legacy_begin();
}
// similarly for begin() taking const&, cbegin(), end(), cend(), etc.
} // namespace legacy
// print.h
template<class C>
void print(C const& c)
{
// bring into scope to fall back on for types without their own namespace non-member begin()/end()
using std::begin;
using std::end;
// works for Standard Containers, C-style arrays and legacy Containers
std::copy(begin(c), end(c), std::ostream_iterator<decltype(*begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and legacy Containers
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
Plusy: w 2007 roku, po raz pierwszy w Polsce, w Polsce i za granicą, w 2009 roku, w Polsce i za granicą.]}
Cons : wymaga użycia deklaracji w wielu miejscach
.begin()
i .end()
legacy::Container<T>
który nie ma członu .begin()
i end()
bez konieczności podawania źródła modyfikacje kodu
std::begin
i std::end
muszą być wprowadzone do każdego jawnego zakresu wywołania jako opcje awaryjne dla tablic w stylu C (potencjalne pułapki dla nagłówków szablonów i ogólne uciążliwości) ADL przez niestandardowe Nie-członka
W przeciwieństwie do innych rozwiązań, nie można ich używać w systemach Windows, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X]} przestrzeń nazw poprzez dostarczenie nieczłonkowych szablonów funkcji adl_begin()
i adl_end()
adl_begin()
i adl_end()
, które następnie można znaleźć również poprzez ADL. Przykładowy kod (na podstawie komentarzy @Yakk):
// LegacyContainerBeginEnd.h
// as before...
// ADLBeginEnd.h
namespace adl {
using std::begin; // <-- here, because otherwise decltype() will not find it
template<class C>
auto adl_begin(C && c) -> decltype(begin(std::forward<C>(c)))
{
// using std::begin; // in C++14 this might work because decltype() is no longer needed
return begin(std::forward<C>(c)); // try to find non-member, fall back on std::
}
// similary for cbegin(), end(), cend(), etc.
} // namespace adl
using adl::adl_begin; // will be visible in any compilation unit that includes this header
// print.h
# include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope
template<class C>
void print(C const& c)
{
// works for Standard Containers, C-style arrays and legacy Containers
std::copy(adl_begin(c), adl_end(c), std::ostream_iterator<decltype(*adl_begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and legacy Containers
// does not need adl_begin() / adl_end(), but continues to work
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
Plusy : spójna konwencja wywołująca, która działa całkowicie generycznie
- te same plusy co za sugestia @Xeo +
- powtarzające się deklaracje użycia zostały zamknięte (suche)
Wady : trochę gadatliwy
-
adl_begin()
/adl_end()
nie jest jak terse asbegin()
/end()
Nie jest też tak samo idiomatyczny (choć jest jednoznaczny).]}
- W oczekiwaniu na dedukcję typu zwracanego przez C++14, będzie również zanieczyszczać przestrzeń nazw za pomocą
std::begin
/std::end
Uwaga: Nie jestem pewien, czy to naprawdę poprawia się w stosunku do poprzedniego podejścia.
std::begin()
lubstd::end()
wszędzie
Raz werbalność begin()
/ end()
została poddana i tak, dlaczego nie wrócić do kwalifikowanych połączeń z std::begin()
/ std::end()
? Przykładowy kod:
// LegacyIntContainerBeginEnd.h
namespace std {
// retro-fitting begin() / end() interface on legacy IntContainer class
// with incompatible names
template<>
auto begin(legacy::IntContainer& c) -> decltype(c.legacy_begin())
{
return c.legacy_begin();
}
// similary for begin() taking const&, cbegin(), end(), cend(), etc.
} // namespace std
// LegacyContainer.h
namespace legacy {
template<class T>
class Container
{
public:
// YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED
auto begin() -> decltype(legacy_begin()) { return legacy_begin(); }
auto end() -> decltype(legacy_end()) { return legacy_end(); }
// rest of existing interface
};
} // namespace legacy
// print.h
template<class C>
void print(C const& c)
{
// works for Standard Containers, C-style arrays as well as
// legacy::IntContainer and legacy::Container<T>
std::copy(std::begin(c), std::end(c), std::ostream_iterator<decltype(*std::begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and
// legacy::IntContainer and legacy::Container<T>
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
Plusy : spójna konwencja wywołująca, która działa prawie generycznie
- działa dla wszystkich standardowych kontenerów i typów użytkowników, które definiują member
.begin()
i.end()
- działa dla tablic w stylu C
W 2007 roku, w ramach programu "Horyzont 2020", w ramach programu "Horyzont 2020", w ramach programu "Horyzont 2020" - program ramowy]}
Podejście ADL przez osoby niebędące członkami Dla kodu, który używa tylko standardowych kontenerów lub tablic w stylu C, W kodzie niestandardowym, gdzie dany kontener jest znany w czasie kodowania, można nawet polegać na ADL tylko dla standardowych kontenerów i wyraźnie kwalifikować
std::begin()
/ std::end()
jest nieco bardziej wyrazisty niż begin()
/ end()
LegacyContainer
który nie ma member .begin()
i end()
(i dla którego nie ma kodu źródłowego!) poprzez podanie jawnych specjalizacji nieczłonkowych szablonów funkcji begin()
i end()
w namespace std
LegacyContainer<T>
poprzez bezpośrednie dodawanie funkcji Członkowskich begin()
/ end()
wewnątrz kodu źródłowego LegacyContainer<T>
(który dla szablonów jest dostępny). namespace std
sztuczka nie działa tutaj, ponieważ szablony funkcji nie mogą być częściowo wyspecjalizowane. Czego używać?
begin()
/ end()
w kontenerze a przestrzeń nazw jest idiomatyczne podejście C++11, szczególnie dla funkcji generycznych, które wymagają modernizacji na starszych klasach i szablonach klas. Jest to ten sam idiom, co dla funkcji nieczłonkowych swap()
udostępniających użytkownika.std::begin()
i std::end()
można nazwać wszędzie bez wprowadzania using-declarations, kosztem bardziej gadatliwych wywołań. To podejście może być nawet zmodernizowane, ale wymaga modyfikowania namespace std
(dla typów klas) lub modyfikowania źródeł (dla szablonów klas). Można to zrobić, ale nie jest warte kłopotów z utrzymaniem.std::begin
/ std::end
dla C-style tablice. Traci pewną spójność wywołania, ale oszczędza na używaniu deklaracji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-11 07:57:24