Dlaczego nie std::initializer_list
ma wbudowanego języka podstawowego?
Wydaje mi się, że jest to dość ważna cecha C ++ 11, a mimo to nie ma własnego zarezerwowanego słowa kluczowego (lub czegoś podobnego).
Zamiast tego initializer_list
jest to po prostu klasa szablonu z biblioteki standardowej, która ma specjalne, niejawne mapowanie z nowej składni listy braced-init, {...}
która jest obsługiwana przez kompilator.
Na pierwszy rzut oka to rozwiązanie jest dość hakerskie .
Czy w ten sposób będą teraz wdrażane nowe dodatki do języka C ++: przez niejawne role niektórych klas szablonów, a nie przez język rdzenia ?
Rozważ te przykłady:
widget<int> w = {1,2,3}; //this is how we want to use a class
dlaczego wybrano nową klasę:
widget( std::initializer_list<T> init )
zamiast używać czegoś podobnego do któregokolwiek z tych pomysłów:
widget( T[] init, int length ) // (1)
widget( T... init ) // (2)
widget( std::vector<T> init ) // (3)
- klasyczną tablicę, którą prawdopodobnie mógłbyś dodać
const
tu i tam - trzy kropki już istnieją w języku (var-args, teraz variadic templates), dlaczego nie użyć ponownie składni (i sprawić, by wyglądała jak wbudowana )
- tylko istniejący kontener, można dodać
const
i&
Wszystkie są już częścią języka. Napisałem tylko swoje 3 pierwsze pomysły, jestem pewien, że istnieje wiele innych podejść.
źródło
std::array<T>
nie jest bardziej „częścią języka” niżstd::initializer_list<T>
. I nie są to prawie jedyne komponenty biblioteki, na których opiera się język. Zobacznew
/delete
,type_info
różne typy wyjątkówsize_t
itp.const T(*)[N]
, ponieważ zachowuje się bardzo podobnie do tego, jakstd::initializer_list
działa.std::array
tablice o statycznym rozmiarze są mniej pożądanymi alternatywami.Odpowiedzi:
Były już przykłady "podstawowych" funkcji języka, które zwracały typy zdefiniowane w
std
przestrzeni nazw.typeid
powracastd::type_info
i (być może rozciągając punkt)sizeof
powracastd::size_t
.W pierwszym przypadku musisz już dołączyć standardowy nagłówek, aby używać tej tak zwanej funkcji „języka podstawowego”.
Teraz w przypadku list inicjalizujących zdarza się, że do wygenerowania obiektu nie jest potrzebne słowo kluczowe, składnia to kontekstowe nawiasy klamrowe. Poza tym to to samo co
type_info
. Osobiście nie sądzę, że brak słowa kluczowego sprawia, że jest to „bardziej hakerskie”. Być może nieco bardziej zaskakujące, ale pamiętaj, że celem było zezwolenie na taką samą składnię inicjalizatora ze wzmocnieniem, która była już dozwolona dla agregacji.Więc tak, prawdopodobnie możesz spodziewać się więcej tej zasady projektowania w przyszłości:
std
zamiast jako wbudowane.W związku z tym:
std
.Myślę, że sprowadza się to do tego, że w C ++ nie ma absolutnego podziału między „językiem podstawowym” a standardowymi bibliotekami. W standardzie są to różne rozdziały, ale każdy z nich odnosi się do siebie nawzajem i zawsze tak było.
W C ++ 11 istnieje inne podejście, polegające na tym, że lambdy wprowadzają obiekty, które mają anonimowe typy generowane przez kompilator. Ponieważ nie mają nazw, w ogóle nie znajdują się w przestrzeni nazw, a już na pewno nie
std
. Nie jest to jednak odpowiednie podejście do list inicjalizujących, ponieważ używasz nazwy typu podczas pisania konstruktora, który ją akceptuje.źródło
type_info
isize_t
są fajnymi argumentami… cóż,size_t
to tylko typedef… więc pomińmy to. Różnica międzytype_info
iinitializer_list
polega na tym, że pierwszy jest wynikiem jawnego operatora, a drugi niejawnej akcji kompilatora. Wydaje mi się również, żeinitializer_list
można by to zastąpić jakimiś już istniejącymi kontenerami ... lub jeszcze lepiej: dowolny użytkownik deklaruje jako typ argumentu!vector
który pobiera an,array
to możesz skonstruować wektor z dowolnej tablicy odpowiedniego typu, a nie tylko wygenerowanej przez składnię listy inicjalizacyjnej. Nie jestem pewien, czy byłoby źle konstruować kontenery z dowolnegoarray
, ale nie jest to intencją komitetu wprowadzającego nową składnię.std::array
nie ma nawet żadnych konstruktorów.std::array
Sprawa jest po prostu agregat inicjalizacja. Zapraszam również do przyłączenia się do czatu Lounge <C ++>, ponieważ ta dyskusja staje się nieco długa.Wydaje się, że Komitet Standardowy C ++ woli nie dodawać nowych słów kluczowych, prawdopodobnie dlatego, że zwiększa to ryzyko złamania istniejącego kodu (starszy kod mógłby używać tego słowa kluczowego jako nazwy zmiennej, klasy lub czegokolwiek innego).
Ponadto wydaje mi się, że zdefiniowanie
std::initializer_list
kontenera z szablonem jest dość eleganckim wyborem: gdyby było to słowo kluczowe, w jaki sposób można uzyskać dostęp do jego bazowego typu? Jak byś to powtórzył? Potrzebowałbyś również kilku nowych operatorów, a to zmusiłoby cię do zapamiętania większej liczby nazw i słów kluczowych, aby zrobić to samo, co w przypadku standardowych kontenerów.Traktowanie
std::initializer_list
kontenera jak każdego innego kontenera daje możliwość napisania ogólnego kodu, który działa z każdą z tych rzeczy.AKTUALIZACJA:
Na początek wszystkie inne kontenery mają metody dodawania, usuwania i umieszczania elementów, które nie są pożądane w przypadku kolekcji generowanej przez kompilator. Jedynym wyjątkiem jest to
std::array<>
, że opakowuje tablicę o stałym rozmiarze w stylu C i dlatego pozostaje jedynym rozsądnym kandydatem.Jednak, jak Nicol Bolas słusznie podkreśla się w komentarzach, inny, zasadnicza różnica pomiędzy
std::initializer_list
i wszystkie inne standardowe pojemniki (włączniestd::array<>
) jest to, że te ostatnie mają semantykę wartości , natomiaststd::initializer_list
ma semantykę referencyjnych . Kopiowaniestd::initializer_list
, na przykład, nie spowoduje kopię poszczególnych pierwiastków.Co więcej (po raz kolejny dzięki uprzejmości Nicol Bolas), posiadanie specjalnego pojemnika na listy inicjalizacji nawiasów klamrowych pozwala na przeciążenie sposobu, w jaki użytkownik wykonuje inicjalizację.
źródło
std::array
. Alestd::array
przydziela pamięć podczasstd::initializaer_list
zawijania tablicy w czasie kompilacji. Potraktuj to jako różnicę międzychar s[] = "array";
ichar *s = "initializer_list";
.std::array
nie alokuje żadnej pamięci, to zwykłaT arr[N];
, ta sama rzecz, która jest kopiąstd::initializer_list
.T arr[N]
alokuje pamięć, może nie w dynamicznej stercie, ale gdzie indziej ... Tak samostd::array
. Jednak użytkownik nieinitializer_list
może skonstruować wartości niepustej, więc oczywiście nie może przydzielić pamięci.To nic nowego. Na przykład
for (i : some_container)
opiera się na istnieniu określonych metod lub samodzielnych funkcji wsome_container
klasie. C # jeszcze bardziej polega na swoich bibliotekach .NET. Właściwie uważam, że jest to dość eleganckie rozwiązanie, ponieważ możesz dostosować swoje zajęcia do niektórych struktur językowych bez komplikowania specyfikacji języka.źródło
begin
iend
metody. To trochę inna IMO.iterable class MyClass { };
initializer_list
jednakTo rzeczywiście nic nowego i ilu z nich zwróciło uwagę, ta praktyka istniała w C ++ i jest, powiedzmy, w C #.
Andrei Alexandrescu wspomniał jednak o dobrej uwadze w tej sprawie: możesz myśleć o niej jako o części wyimaginowanej „rdzeniowej” przestrzeni nazw, wtedy będzie to miało więcej sensu.
Tak, to rzeczywiście coś takiego:
core::initializer_list
,core::size_t
,core::begin()
,core::end()
i tak dalej. To tylko niefortunny zbieg okoliczności, żestd
przestrzeń nazw zawiera w sobie pewne podstawowe konstrukcje językowe.źródło
Nie tylko może działać całkowicie w bibliotece standardowej. Włączenie do standardowej biblioteki nie oznacza, że kompilator nie potrafi płatać sprytnych sztuczek.
Chociaż może nie być w stanie tego zrobić we wszystkich przypadkach, może bardzo dobrze powiedzieć: ten typ jest dobrze znany lub typ prosty, zignorujmy
initializer_list
i po prostu miej w pamięci obraz tego, jaka powinna być zainicjowana wartość.Innymi słowy
int i {5};
może być równoważneint i(5);
lubint i=5;
nawetintwrapper iw {5};
Gdzieintwrapper
jest prosty wrapper klasy na int z trywialny konstruktor odbywającychinitializer_list
źródło
int i {5}
obejmuje cokolwiek,std::initializer_list
jest błędny.int
nie ma konstruktorastd::initializer_list
, więc5
jest używany bezpośrednio do konstruowania. Więc główny przykład jest nieistotny; nie ma po prostu żadnej optymalizacji do wykonania. Poza tym, ponieważstd::initializer_list
wiąże się to z tworzeniem przez kompilator i proxy „wyimaginowanej” tablicy, myślę, że może sprzyjać optymalizacji, ale to jest „magiczna” część kompilatora, więc jest to niezależne od tego, czy ogólnie optymalizator może zrobić coś sprytnego z ładnym nudny obiekt zawierający 2 iteratory, które wynikająNie jest częścią podstawowego języka, ponieważ można go zaimplementować w całości w bibliotece, po prostu w wierszach
operator new
ioperator delete
. Jakie korzyści przyniosłoby utrudnienie kompilatorom kompilacji?źródło