Liste başlatma (kıvırcık parantez kullanarak) neden alternatiflerden daha iyi?


406
MyClass a1 {a};     // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

Neden?

SO ile ilgili bir cevap bulamadım, bu yüzden kendi sorumu yanıtlamama izin verin.


12
Neden kullanmıyorsunuz auto?
Mark Garcia

33
Bu doğru, kullanışlı, ama bence okunabilirliği azaltır - Kod okurken bir nesnenin ne tür olduğunu görmek istiyorum . Nesnenin ne tür olduğundan% 100 eminseniz, neden otomatik kullanıyorsunuz? Ve liste başlatmayı kullanırsanız (cevabımı okuyun), her zaman doğru olduğundan emin olabilirsiniz.
Oleksiy

102
@Oleksiy: Seninle std::map<std::string, std::vector<std::string>>::const_iteratorbir kelime istiyorum.
Xeo

9
@Oleksiy Bu GotW'ı okumanızı tavsiye ederim .
Rapptz

17
@doc söyleyebilirim using MyContainer = std::map<std::string, std::vector<std::string>>;daha iyi (özellikle bunu şablon olarak!)
JAB

Yanıtlar:


357

Temel olarak Bjarne Stroustrup'un "The C ++ Programming Language 4th Edition" dan kopyalanması ve yapıştırılması :

Listenin başlatılması daralmaya izin vermemektedir (§iso.8.5.4). Yani:

  • Bir tam sayı, değerini tutamayan başka bir tam sayıya dönüştürülemez. Örneğin, char'dan int'e izin verilir, ancak char'a int için izin verilmez.
  • Kayan nokta değeri, değerini tutamayan başka bir kayan nokta türüne dönüştürülemez. Örneğin, şamandıra iki katına izin verilir, ancak iki kat yüzmeye izin verilmez.
  • Kayan nokta değeri bir tamsayı tipine dönüştürülemez.
  • Bir tam sayı değeri kayan nokta türüne dönüştürülemez.

Misal:

void fun(double val, int val2) {

    int x2 = val; // if val==7.9, x2 becomes 7 (bad)

    char c2 = val2; // if val2==1025, c2 becomes 1 (bad)

    int x3 {val}; // error: possible truncation (good)

    char c3 {val2}; // error: possible narrowing (good)

    char c4 {24}; // OK: 24 can be represented exactly as a char (good)

    char c5 {264}; // error (assuming 8-bit chars): 264 cannot be 
                   // represented as a char (good)

    int x4 {2.0}; // error: no double to int value conversion (good)

}

Sadece kullanırken = daha fazla tercih edilir bir durum {} olan autobaşlatıcısı tarafından belirlenen tipini almak için anahtar kelime.

Misal:

auto z1 {99};   // z1 is an int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99;   // z3 is an int

Sonuç

Güçlü bir nedeniniz olmadıkça {} alternatiflere göre başlatmayı tercih edin.


52
Ayrıca, ()bir fonksiyon bildirimi olarak kullanımın ayrıştırılabileceği gerçeği de vardır . Söyleyebileceğiniz T t(x,y,z);ama yapamayacağınız kafa karıştırıcı ve tutarsız T t(). Ve bazen, kesin x, söyleyemezsin bile T t(x);.
juanchopanza

84
Bu cevaba kesinlikle katılmıyorum; kabul edilen bir ctor ile türleriniz olduğunda hazır başlatma tam bir karmaşaya dönüşür std::initializer_list. RedXIII bu sorundan bahseder (ve sadece fırçalar), oysa tamamen görmezden gelirsiniz. A(5,4)ve A{5,4}tamamen farklı işlevler olarak adlandırılabilir ve bu bilmek önemli bir şeydir. Hatta sezgisel olmayan çağrılarla bile sonuçlanabilir. {}Varsayılan olarak tercih etmeniz gerektiğini söylemek, insanların neler olduğunu yanlış anlamalarına yol açacaktır. Bu senin hatan değil. Şahsen bunun son derece kötü düşünülmüş bir özellik olduğunu düşünüyorum.
user1520427

13
@ user1520427 Bu yüzden " güçlü bir nedeniniz olmadıkça " bölümü var.
Oleksiy

67
Bu soru eski olmasına rağmen oldukça az hit var, bu yüzden sadece referans için buraya ekliyorum (sayfada başka bir yerde görmedim). C ++ ile yeni 14'ten hazırladı-init-listeden otomatik kesinti için Kurallar yazmak için artık mümkün auto var{ 5 }ve o kadar çıkarılabilir edilecektir intartık olarak std::initializer_list<int>.
Edoardo Sparkon Dominici

12
Haha, tüm yorumlardan hala ne yapılacağı belli değil. Açık olan, C ++ belirtiminin bir karmaşa olduğu!
DrumM

113

Liste başlatmanın kullanılmasının avantajları hakkında zaten büyük cevaplar var, ancak kişisel başparmak kuralım mümkün olduğunda kıvırcık parantez kullanmak DEĞİL, bunun yerine kavramsal anlama bağlı hale getirmek:

  • Oluşturduğum nesne kavramsal olarak yapıcıda geçirdiğim değerleri tutarsa ​​(örn. Kaplar, POD yapıları, atomlar, akıllı işaretçiler vb.), Kaşlı ayraçları kullanıyorum.
  • Yapıcı normal bir işlev çağrısına benziyorsa (bağımsız değişkenler tarafından parametrelenen az çok karmaşık işlemleri gerçekleştirirse) normal işlev çağrısı sözdizimini kullanıyorum.
  • Varsayılan başlatma için her zaman kıvırcık parantez kullanırım.
    Birincisi, bu şekilde her zaman, örneğin, yine de çağrılacak varsayılan bir kurucu veya bir yerleşik / POD türü ile "gerçek" bir sınıf olup olmadığına bakılmaksızın nesnenin başlatıldığından eminim. İkincisi - çoğu durumda - ilk kuralla tutarlıdır, çünkü varsayılan olarak başlatılmış bir nesne genellikle "boş" bir nesneyi temsil eder.

Deneyimlerime göre, bu kural seti varsayılan olarak kıvırcık parantez kullanmaktan çok daha tutarlı bir şekilde uygulanabilir, ancak kullanılamadığında veya istisna "normal" işlev çağrısı sözdiziminden farklı bir anlamı olduğunda tüm istisnaları açıkça hatırlamak zorunda (farklı bir aşırı yük çağırır).

Örneğin, aşağıdaki gibi standart kütüphane türlerine çok uygundur std::vector:

vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments

vector<int> b(10,20);   //Parentheses -> uses arguments to parametrize some functionality,                          
vector<int> c(it1,it2); //like filling the vector with 10 integers or copying a range.

vector<int> d{};      //empty braces -> default constructs vector, which is equivalent
                      //to a vector that is filled with zero elements

11
Cevabınızın çoğuna tamamen katılıyorum. Ancak, vektör için boş parantez koymanın gereksiz olduğunu düşünmüyor musunuz? Yani, T türü genel bir nesneyi değer başlatmanız gerektiğinde sorun değil, ancak genel olmayan kod için bunu yapmanın amacı nedir?
Mikhail

8
@Mikhail: Kesinlikle gereksizdir, ancak yerel değişken başlatmayı her zaman açık hale getirmek benim alışkanlığımdır. Yazdığım gibi, bu esas olarak tutarlılık ile ilgilidir, bu yüzden önemli olduğunda unutmam. Kesinlikle bir kod inceleme bahsetmek ya da bir stil Kılavuzu olsa koymak bir şey değil.
MikeMB

4
oldukça temiz kurallar.
laike9m

5
Bu açık ara en iyi cevap. {} kalıtım gibidir - kötüye kullanımı kolaydır, anlaşılması zor kodlara yol açar.
UKMonkey

2
@MikeMB örneği: const int &b{}<- başlatılmamış bir başvuru oluşturmaya çalışmaz, ancak geçici bir tamsayı nesnesine bağlar. İkinci örnek: struct A { const int &b; A():b{} {} };<- (olduğu gibi ()) başlatılmamış bir başvuru oluşturmaya çalışmaz , ancak geçici bir tamsayı nesnesine bağlar ve sonra sarkık bırakır. GCC bile -Wallikinci örnek için uyarmaz.
Johannes Schaub - litb

91

Orada kullanım ayracı başlatma için birçok neden vardır, ancak bilmelidir yapıcı diğer kurucular tercih edilir istisna varsayılan-yapıcı olmak. Bu, tür yapıcısının bir başlatıcı listesi veya düz eski bir ctor olabileceği yapıcılar ve şablonlarla ilgili sorunlara yol açar .initializer_list<>T

struct Foo {
    Foo() {}

    Foo(std::initializer_list<Foo>) {
        std::cout << "initializer list" << std::endl;
    }

    Foo(const Foo&) {
        std::cout << "copy ctor" << std::endl;
    }
};

int main() {
    Foo a;
    Foo b(a); // copy ctor
    Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}

Bu tür sınıflarla karşılaşmadığınızı varsayarsak, intializer listesini kullanmamak için çok az neden vardır.


20
Genel programlamada bu çok önemli bir noktadır. Şablonları yazarken yok hazırladı-init-listeleri (için standardın adı kullanmak { ... }istediğiniz sürece) initializer_list(varsayılan-inşa bir nesne için belki de, ve) anlambilim.
Xeo

82
Dürüst olmak gerekirse, std::initializer_listkuralın neden var olduğunu bile anlamıyorum - sadece dile karışıklık ve karışıklık katıyor. Yapıcıyı Foo{{a}}istiyorsan ne yapmanın yanlış olduğunu std::initializer_list? Anlamak, std::initializer_listdiğer tüm aşırı yüklenmelere göre öncelikli olmaktan çok daha kolay görünüyor .
user1520427

5
Yukarıdaki yorum için +1, çünkü onun gerçekten karışıklık düşünüyorum! mantık değildir; Foo{{a}}benim için bazı mantık çok daha takip Foo{a}eden intializer listesi önceliğe dönüşür (ups kullanıcı düşünebilirsiniz hm ...)
Gabriel

32
Temel olarak C ++ 11 bir karmaşayı başka bir karmaşa ile değiştirir. Oh, üzgünüm onun yerine geçmez - ekler. Böyle sınıflarla karşılaşmadığınızı nasıl anlarsınız? Yapıcı olmadan başlarsanız std::initializer_list<Foo>, ancak arabirimini genişletmek için bir noktada sınıfa eklenecekse ne olur Foo? Sonra Foosınıf kullanıcıları mahvoldu.
doc

10
.. "Küme ayracı başlatma kullanmanın birçok nedeni" nelerdir? Bir nedenle (dışarı Bu cevap noktaları initializer_list<>gerçekten hak değil), kim o zaman tercih söylüyor ve tek kişi iyi durumda söz ilerler DEĞİL tercih etti. ~ 30 diğer insanın (2016-04-21 itibariyle) faydalı bulduğunu ne özlüyorum?
dwanderson

0

Google'ın Chromium'da yaptığı gibi -Wno-daralma ile oluşturmadığınız sürece daha güvenlidir. Eğer yaparsanız, o zaman daha az güvenlidir. Bu bayrak olmadan, sadece güvensiz durumlar C ++ 20 tarafından düzeltilecektir.

Not: A) Kıvırcık parantezler daha da güvenlidir, çünkü daralmaya izin vermezler. B) Kıvırcık parantezler daha az güvenlidir, çünkü özel veya silinmiş kurucuları atlayabilir ve açık işaretli kurucuları dolaylı olarak çağırabilirler.

Bu iki birleşik, içerideki şey ilkel sabitler ise daha güvenli, ancak nesneler ise daha az güvenli oldukları anlamına gelir (C ++ 20'de düzeltilmiş olsa da)


Sağlanan örnek kodunu kullanarak "açık" veya "özel" kurucuları atlamak için goldbolt.org'da erişmeyi denedim ve bir veya diğerini özel veya açık hale getirdim ve uygun derleyici hatası ile ödüllendirildim. Bazı örnek kodlarla yedeklemeyi ister misiniz?
Mark Storer

Bu, C ++ 20 için önerilen sorunun düzeltilmesidir: open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1008r1.pdf
Allan Jensen

1
Yanıtınızı C ++ 'ın hangi sürümlerinden bahsettiğinizi göstermek için düzenlerseniz, oyumu değiştirmekten memnuniyet duyarız.
Mark Storer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.