Ayraçlı başlatıcı ne zaman kullanılır?


97

C ++ 11'de, sınıfları başlatmak için, değişkenleri nasıl başlatacağımız konusunda bize çok sayıda olasılık veren yeni sözdizimimiz var.

{ // 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};
}

Açıkladığım her değişken için, hangi ilkleme sözdizimini kullanmam gerektiğini düşünmem gerekiyor ve bu benim kodlama hızımı yavaşlatıyor. Küme parantezlerini tanıtmak niyetinin bu olmadığına eminim.

Şablon kodu söz konusu olduğunda, sözdizimini değiştirmek farklı anlamlara yol açabilir, bu nedenle doğru yoldan gitmek çok önemlidir.

Birinin seçmesi gereken sözdizimi evrensel bir kılavuz olup olmadığını merak ediyorum.


1
{} Başlatma: string (50, 'x') - string {50, 'x'} arasındaki istenmeyen davranış örneği burada
P i

Yanıtlar:


65

Ben düşünüyorum şu iyi kılavuz olabilir:

  • Başlattığınız (tek) değerin nesnenin tam değeri olması amaçlanıyorsa , copy ( =) başlatmayı kullanın (çünkü bu durumda hata durumunda, genellikle sağlanan değeri yorumlayan açık bir kurucuyu asla yanlışlıkla çağırmazsınız. farklı). Kopya başlatmanın mümkün olmadığı yerlerde, küme ayracı başlatmanın doğru anlamlara sahip olup olmadığına bakın ve eğer öyleyse, bunu kullanın; aksi takdirde parantez başlatma kullanın (eğer bu da yoksa, yine de şansınız kalmaz).

  • Başlattığınız değerler nesnede saklanacak değerlerin bir listesiyse ( bir vektör / dizinin öğeleri veya karmaşık bir sayının gerçek / sanal kısmı gibi), varsa küme parantezlerini kullanın.

  • Başlattığınız değerler depolanacak değerler değilse , ancak nesnenin amaçlanan değerini / durumunu açıklıyorsa , parantez kullanın. Örnekler, a'nın boyut bağımsız değişkeni vectorveya bir fstream.


4
@ user1304032: Yerel ayar bir dizge değildir, bu nedenle kopya başlatmayı kullanmazsınız. Bir yerel ayar ayrıca bir dize içermez (bu dizeyi uygulama ayrıntısı olarak saklayabilir, ancak amacı bu değildir), bu nedenle küme ayracı başlatmayı kullanmazsınız. Bu nedenle kılavuz, parantez başlatmanın kullanılmasını söylüyor.
celtschk

2
Bu kılavuzu kişisel olarak en çok beğendim ve aynı zamanda genel kod üzerinde de iyi çalışıyor. Bazı istisnalar ( T {}veya en sinir bozucu çözümleme gibi sözdizimsel nedenler ) vardır, ancak genel olarak bunun iyi bir tavsiye olduğunu düşünüyorum. Bunun benim öznel görüşüm olduğuna dikkat edin, bu nedenle diğer yanıtlara da bir göz atmalısınız.
helami

2
@celtschk: Kopyalanamayan, taşınamayan türlerde işe yaramaz; type var{};yapar.
ildjarn

2
@celtschk: Bunun sık sık meydana gelen bir şey olduğunu söylemiyorum ama daha az yazıyor ve daha fazla bağlamda çalışıyor, peki dezavantajı nedir?
ildjarn

2
Benim kurallar kesinlikle kopya başlatılması için hiç aramıyorsun. ; -]
ildjarn

27

Asla evrensel bir kılavuz olmayacağından oldukça eminim. Benim yaklaşımım, bunu hatırlayarak her zaman küme parantezi kullanmaktır.

  1. Başlatıcı listesi oluşturucuları diğer oluşturuculara göre önceliklidir
  2. Tüm standart kitaplık kapsayıcıları ve std :: basic_string, başlatıcı liste yapıcılarına sahiptir.
  3. Küme ayracı başlatma, daraltılmış dönüşümlere izin vermez.

Bu yüzden yuvarlak ve küme parantezleri birbirinin yerine kullanılamaz. Ancak nerede farklı olduklarını bilmek çoğu durumda kıvrımlı parantez başlangıcını kullanmama izin veriyor (şu anda yapamadığım bazı durumlarda derleyici hataları)


7
Kıvırcık parantezlerin yanlışlıkla liste oluşturucuyu çağırabilmem dezavantajı vardır. Yuvarlak parantezler yok. Bu, yuvarlak parantezleri varsayılan olarak kullanmak için bir neden değil mi?
helami

4
@user: int i = 0;Kimsenin int i{0}orada kullanacağını sanmıyorum ve kafa karıştırıcı olabilir (ayrıca, 0eğer tür ise int, bu yüzden daraltma olmazdı ). Her şey için Juancho'nun tavsiyesine uyardım: {} 'ı tercih edin, yapmamanız gereken birkaç duruma dikkat edin. Yapıcı bağımsız değişkenleri olarak başlatıcı listelerini alacak pek çok tür olmadığını unutmayın, kapsayıcıların ve kapsayıcı benzeri türlerin (tuple ...) bunlara sahip olmasını bekleyebilirsiniz, ancak çoğu kod uygun kurucuyu çağıracaktır.
David Rodríguez - dribeas

3
@ user1304032 daraltmayı önemsemeye bağlı. Yapıyorum, bu yüzden derleyicinin bana int i{some floating point}sessizce kısaltmak yerine bunun bir hata olduğunu söylemesini tercih ederim .
juanchopanza

3
"Tercih {} ile ilgili olarak, yapmamanız gereken birkaç duruma dikkat edin": Diyelim ki iki sınıfın semantik olarak eşdeğer bir kurucusu var, ancak bir sınıfın da bir başlatıcı listesi var. İki eşdeğer kurucu farklı şekilde çağrılmalı mı?
helami

3
@helami: "Diyelim ki iki sınıfın semantik olarak eşdeğer bir kurucusu var, ancak bir sınıfın da bir başlatıcı listesi var. İki eşdeğer kurucu farklı şekilde çağrılmalı mı?" En can sıkıcı çözümlemeyle karşılaştığımı varsayalım; bu, herhangi bir örnek için herhangi bir kurucuda gerçekleşebilir. {}Kesinlikle yapamadığınız sürece, yalnızca "başlat" demek için kullanırsanız, bundan kaçınmak çok daha kolaydır .
Nicol Bolas

16

Genel kod (ör. Şablonlar) dışında, her yerde parantez kullanabilirsiniz (ve ben de kullanıyorum) . Bir avantajı, örneğin sınıf içi başlatma için bile her yerde çalışmasıdır:

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";
};

veya işlev bağımsız değişkenleri için:

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));

T t = { init };Veya T t { init };stilleri arasında fazla dikkat etmediğim değişkenler için, farkı küçük buluyorum ve en kötü ihtimalle yalnızca bir kurucunun kötüye kullanılması hakkında yararlı bir derleyici mesajı ile sonuçlanacaktır explicit.

Kabul eden türler için, std::initializer_listaçıkça bazen std::initializer_listyapıcı olmayanlara ihtiyaç duyulur (klasik örnek std::vector<int> twenty_answers(20, 42);). O zaman diş teli kullanmamak sorun değil.


Genel kod söz konusu olduğunda (ör. Şablonlarda) son paragraf bazı uyarılara neden olmalıydı. Aşağıdakileri göz önünde bulundur:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }

Sonra auto p = make_unique<std::vector<T>>(20, T {});eğer büyüklüğü 2'nin bir vektör oluşturur Törn olduğunu intveya olmadığını büyüklüğü 20 bir vektör Tolduğunu std::string. Burada çok ters giden bir şeyler olduğuna dair çok açık bir işaret, sizi burada kurtarabilecek hiçbir özellik olmadığıdır (örneğin SFINAE ile): std::is_constructibledoğrudan başlatma anlamındadır, oysa biz yönlendirmeyi erteleyen küme ayracı başlatmayı kullanıyoruz. başlatma, ancak ve ancak kurucu std::initializer_listmüdahale eden yoksa . Benzer şekilde std::is_convertiblehiçbir faydası yok.

Bunu düzeltebilecek bir özelliği elden geçirmenin gerçekten mümkün olup olmadığını araştırdım, ancak bu konuda aşırı iyimser değilim. Her halükarda çok şey kaçıracağımızı düşünmüyorum, buna make_unique<T>(foo, bar)eşdeğer bir yapı ile sonuçlanmanın T(foo, bar)çok sezgisel olduğunu düşünüyorum; Özellikle göz önüne alındığında make_unique<T>({ foo, bar })oldukça farklı olup olmadığını sadece mantıklı foove baraynı tür.

Bu nedenle , genel kod için sadece değer başlatma (örneğin T t {};veya T t = {};) için parantez kullanıyorum , bu çok uygun ve bence C ++ 03 yolundan daha üstün T t = T();. Aksi takdirde, ya doğrudan başlatma sözdizimi (yani T t(a0, a1, a2);) ya da bazen varsayılan yapı ( T t; stream >> t;düşündüğümde kullandığım tek durum).

Bu, tüm parantezlerin kötü olduğu anlamına gelmez, düzeltmelerle önceki örneği düşünün:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }

Bu std::unique_ptr<T>, gerçek tür şablon parametresine bağlı olsa da, yine de köşeli ayraçları kullanır T.


@interjay Örneklerimden bazıları gerçekten de işaretsiz türler kullanmak zorunda olabilir, örneğin ya da olmak make_unique<T>(20u, T {})için . Ayrıntılardan pek emin değilim. (Örneğin mükemmel iletme işlevleriyle ilgili olarak doğrudan başlatma ve küme ayracı başlatma ile ilgili beklentiler hakkında da yorum yaptığımı unutmayın.) , Dilbilgisindeki üye işlev bildirimleriyle ilgili belirsizliklerden kaçınmak için sınıf içi bir başlatma olarak çalışacak şekilde belirtilmemiştir. Tunsignedstd::stringstd::string c("qux");
Luc Danton

@interjay İlk noktada sizinle aynı fikirde değilim, 8.5.4 Liste başlatma ve liste başlatma ile 13.3.1.7 Başlatma kontrol etmekten çekinmeyin. İkincisi ise, yazdıklarıma ( sınıf içi başlatma ile ilgili) ve / veya C ++ dilbilgisine (örneğin , parantez-veya-eşit-başlatıcıya başvuran üye tanımlayıcı ) daha yakından bakmanız gerekir .
Luc Danton

Hmm, haklısın - daha önce GCC 4.5 ile test ediyordum ki bu söylediğimi doğruluyor gibiydi, ancak GCC 4.6 sizinle aynı fikirde. Ve sınıf içi başlatma hakkında konuştuğunuz gerçeğini özledim. Özür dilerim.
interjay
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.