W C ++ 11 mamy nową składnię do inicjowania klas, która daje nam dużą liczbę możliwości inicjalizacji zmiennych.
{ // Example 1
int b(1);
int a{1};
int c = 1;
int d = {1};
}
{ // Example 2
std::complex<double> b(3,4);
std::complex<double> a{3,4};
std::complex<double> c = {3,4};
auto d = std::complex<double>(3,4);
auto e = std::complex<double>{3,4};
}
{ // Example 3
std::string a(3,'x');
std::string b{3,'x'}; // oops
}
{ // Example 4
std::function<int(int,int)> a(std::plus<int>());
std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
std::unique_ptr<int> a(new int(5));
std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
std::locale::global(std::locale("")); // copied from 22.4.8.3
std::locale::global(std::locale{""});
}
{ // Example 7
std::default_random_engine a {}; // Stroustrup's FAQ
std::default_random_engine b;
}
{ // Example 8
duration<long> a = 5; // Stroustrup's FAQ too
duration<long> b(5);
duration<long> c {5};
}
Dla każdej deklarowanej przeze mnie zmiennej muszę pomyśleć, której składni inicjalizującej powinienem użyć, a to spowalnia moją szybkość kodowania. Jestem pewien, że nie było to intencją wprowadzenia nawiasów klamrowych.
Jeśli chodzi o kod szablonu, zmiana składni może prowadzić do różnych znaczeń, więc podążanie właściwą drogą jest niezbędne.
Zastanawiam się, czy istnieje uniwersalna wskazówka, jaką składnię należy wybrać.
c++
c++11
initializer-list
helami
źródło
źródło
Odpowiedzi:
Myślę, że następujące wskazówki mogą być dobrą wskazówką:
Jeśli (pojedyncza) wartość, z którą inicjujesz, ma być dokładną wartością obiektu, użyj
=
inicjalizacji copy ( ) (ponieważ wtedy w przypadku błędu nigdy przypadkowo nie wywołasz jawnego konstruktora, który generalnie interpretuje podaną wartość różnie). W miejscach, gdzie inicjalizacja kopiowania nie jest dostępna, sprawdź, czy inicjalizacja nawiasów klamrowych ma poprawną semantykę, a jeśli tak, użyj jej; w przeciwnym razie użyj inicjalizacji nawiasów (jeśli to również nie jest dostępne, i tak nie masz szczęścia).Jeśli wartości, z którymi inicjujesz, są listą wartości, które mają być przechowywane w obiekcie (jak elementy wektora / tablicy lub rzeczywista / urojona część liczby zespolonej), użyj nawiasów klamrowych, jeśli są dostępne.
Jeśli wartości, z którymi inicjujesz, nie są wartościami do zapisania, ale opisują zamierzoną wartość / stan obiektu, użyj nawiasów. Przykładami są argument rozmiaru argumentu
vector
lub argument nazwy pliku elementufstream
.źródło
T {}
lub powodów składniowych, takich jak najbardziej irytująca analiza ), ale ogólnie uważam, że to dobra rada. Zauważ, że jest to moja subiektywna opinia, więc warto przyjrzeć się również pozostałym odpowiedziom.type var{};
robi.Jestem prawie pewien, że nigdy nie będzie uniwersalnej wytycznej. Moje podejście polega na używaniu zawsze aparatu kręconego, pamiętając o tym
Dlatego szelki okrągłe i kręcone nie są wymienne. Ale wiedza o tym, gdzie się różnią, pozwala mi w większości przypadków użyć inicjalizacji nawiasów klamrowych nad nawiasami okrągłymi (niektóre z przypadków, w których nie mogę, są obecnie błędami kompilatora).
źródło
int i = 0;
Nie sądzę, żeby ktokolwiekint i{0}
tam używał , i może to być mylące (również0
jest typu ifint
, więc nie byłoby zwężenia ). W pozostałych przypadkach postąpiłbym zgodnie z radą Juancho: preferuj {}, uważaj na kilka przypadków, w których nie powinieneś. Zwróć uwagę, że nie ma zbyt wielu typów, które przyjmą listy inicjatorów jako argumenty konstruktorów, możesz oczekiwać, że kontenery i typy kontenerowe (krotka ...) będą je mieć, ale większość kodu wywoła odpowiedni konstruktor.int i{some floating point}
to błąd, zamiast po cichu skracać.{}
określenia „zainicjuj”, chyba że absolutnie nie możesz .Poza kodem ogólnym (tj. Szablonami) możesz (i ja używamy) wszędzie używać nawiasów klamrowych . Zaletą jest to, że działa wszędzie, na przykład nawet w przypadku inicjalizacji w klasie:
struct foo { // Ok std::string a = { "foo" }; // Also ok std::string b { "bar" }; // Not possible std::string c("qux"); // For completeness this is possible std::string d = "baz"; };
lub dla argumentów funkcji:
void foo(std::pair<int, double*>); foo({ 42, nullptr }); // Not possible with parentheses without spelling out the type: foo(std::pair<int, double*>(42, nullptr));
W przypadku zmiennych nie zwracam zbytniej uwagi między stylami
T t = { init };
lubT t { init };
, uważam, że różnica jest niewielka i w najgorszym przypadku spowoduje tylko pomocny komunikat kompilatora o niewłaściwym użyciuexplicit
konstruktora.Dla typów, które akceptują,
std::initializer_list
choć oczywiście czasamistd::initializer_list
potrzebne są konstruktory (na przykład klasycznystd::vector<int> twenty_answers(20, 42);
). Dobrze jest wtedy nie używać aparatu ortodontycznego.Jeśli chodzi o kod ogólny (np. W szablonach), ostatni akapit powinien wywołać pewne ostrzeżenia. Rozważ następujące:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }
Następnie
auto p = make_unique<std::vector<T>>(20, T {});
tworzy wektor o rozmiarze 2, jeśliT
jest np.int
, Lub wektor o rozmiarze 20, jeśliT
jeststd::string
. Bardzo charakterystycznym znakiem, że dzieje się tutaj coś bardzo złego, jest to, że nie ma cechy, która może cię tutaj uratować (np. Z SFINAE):std::is_constructible
dotyczy inicjalizacji bezpośredniej, podczas gdy używamy inicjalizacji nawiasami klamrowymi, która odracza do inicjalizacja wtedy i tylko wtedy, gdy żaden konstruktor niestd::initializer_list
przeszkadza. Podobniestd::is_convertible
nie pomoże.Zbadałem, czy rzeczywiście można ręcznie rzucić cechę, która może to naprawić, ale nie jestem co do tego zbyt optymistyczny. W każdym razie nie sądzę, żeby wiele nam brakowało, myślę, że fakt, że
make_unique<T>(foo, bar)
wynikiem jest konstrukcja równoważna z,T(foo, bar)
jest bardzo intuicyjny; zwłaszcza biorąc pod uwagę, żemake_unique<T>({ foo, bar })
jest zupełnie niepodobne i ma sens tylko jeślifoo
ibar
tego samego typu.Dlatego w przypadku kodu ogólnego używam tylko nawiasów klamrowych do inicjowania wartości (np.
T t {};
LubT t = {};
), co jest bardzo wygodne i wydaje mi się, że jest lepsze niż sposób C ++ 03T t = T();
. W przeciwnym razie jest to albo bezpośrednia składnia inicjalizacji (tj.T t(a0, a1, a2);
), Albo czasami konstrukcja domyślna (T t; stream >> t;
jest to jedyny przypadek, w którym używam tego, co myślę).Nie oznacza to jednak, że wszystkie nawiasy klamrowe są złe, rozważ poprzedni przykład z poprawkami:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }
To nadal używa nawiasów klamrowych do konstruowania
std::unique_ptr<T>
, mimo że rzeczywisty typ zależy od parametru szablonuT
.źródło
make_unique<T>(20u, T {})
abyT
być albounsigned
albostd::string
. Nie jestem pewien szczegółów. (Zauważ, że skomentowałem również oczekiwania dotyczące inicjalizacji bezpośredniej w porównaniu z inicjalizacją nawiasów klamrowych dotyczących np. Funkcji doskonałego przekazywania).std::string c("qux");
Nie określono, aby działał jako inicjalizacja w klasie, aby uniknąć niejednoznaczności z deklaracjami funkcji składowych w gramatyce.