C ++ 11 neden C99 olarak belirlenmiş başlatıcı listelerini desteklemiyor? [kapalı]


121

Düşünmek:

struct Person
{
    int height;
    int weight;
    int age;
};

int main()
{
    Person p { .age = 18 };
}

Yukarıdaki kod C99'da yasaldır, ancak C ++ 11'de yasal değildir.

Neydi standart komitenin bu kadar kullanışlı bir özelliği desteklememesinin mantığı?


10
Görünüşe göre tasarım komitesinin bunu dahil etmesi mantıklı gelmedi ya da toplantılarda gündeme gelmedi. C99 tarafından belirlenmiş başlatıcıların hiçbir C ++ belirtim sürümünde olmadığını belirtmek gerekir . Oluşturucular, tercih edilen başlatma yapısı gibi görünüyor ve iyi bir nedenden ötürü: doğru yazarsanız tutarlı nesne başlatmayı garanti ederler.
Robert Harvey

19
Muhakemeniz geriye dönüktür, bir dilin bir özelliğe sahip olmaması için bir mantığı olması gerekmez, bir tane olması için bir mantığa ve bunda güçlü bir mantığa ihtiyacı vardır. C ++ olduğu gibi yeterince şişirilmiş.
Matthieu M.

42
İyi bir neden (aptalca sarmalayıcılar yazmak dışında kurucularla çözülemez), C ++ kullansanız da kullanmasanız da, çoğu gerçek API'nin C ++ değil C olmasıdır ve bunlardan çok azı, ayarlamak istediğiniz bir yapı sağlamanızı sağlamaz. bir veya iki alan - ve ilki değil - ancak geri kalanının sıfır başlatılması gerekir. Win32 API OVERLAPPEDböyle bir örnektir. Yazabilmek ={.Offset=12345};, kodu daha net (ve muhtemelen daha az hataya açık hale getirir). BSD soketleri benzer bir örnektir.
Damon

14
Kod mainyasal C99 değil. Bunu şöyle olmalıdır struct Person p = { .age = 18 };
chqrlie

14
FYI C ++ 20 belirlenen başlatıcıları destekleyecek
Andrew Tomazos

Yanıtlar:


34

C ++ 'ın yapıcıları vardır. Sadece bir üyeyi başlatmak mantıklıysa, bu, uygun bir kurucu uygulayarak programda ifade edilebilir. Bu, C ++ 'nın teşvik ettiği türden bir soyutlamadır.

Öte yandan, atanmış başlatıcılar özelliği daha çok açığa çıkarma ve üyelerin doğrudan istemci kodundan erişmesini kolaylaştırma ile ilgilidir. Bu, 18 yaşında (yaşında?) Ama boyu ve ağırlığı sıfır olan bir kişiye sahip olmak gibi şeylere yol açar.


Başka bir deyişle, atanmış başlatıcılar, dahili öğelerin açığa çıktığı bir programlama stilini destekler ve müşteriye, türü nasıl kullanmak istediğine karar verme esnekliği verilir.

C ++, esnekliği bir tipin tasarımcısının tarafına koymakla daha çok ilgilenir , böylece tasarımcılar bir türü doğru kullanmayı kolaylaştırabilir ve yanlış bir şekilde kullanmayı zorlaştırabilir. Tasarımcıyı bir türün nasıl başlatılabileceği konusunda kontrol altına almak bunun bir parçasıdır: tasarımcı kurucuları, sınıf içi başlatıcıları vb. Belirler.


12
Lütfen C ++ 'nın belirlenmiş başlatıcılara sahip olmamasının nedeni olduğunu söylediğiniz şey için bir referans bağlantısı gösterin. Bunun teklifini hiç gördüğümü hatırlamıyorum.
Johannes Schaub - litb

20
PersonYazarının, kullanıcıların üyeleri ayarlaması ve başlatması için mümkün olan en fazla esnekliği sağlamak istemesinin bir kurucu sağlamamasının nedeni bu değil mi? Kullanıcı zaten yazabilir Person p = { 0, 0, 18 };(ve iyi nedenlerle).
Johannes Schaub - litb

7
Benzer bir şey yakın zamanda open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.html tarafından C ++ 14 spesifikasyonuna kabul edildi .
Johannes Schaub -

4
@ JohannesSchaub-litb Tamamen mekanik, yakın nedenden bahsetmiyorum (yani, komiteye önerilmemiştir). Hakim faktör olduğuna inandığım şeyi açıklıyorum. - Personçok C tasarımına sahip olduğundan C özellikleri mantıklı olabilir. Bununla birlikte, C ++ muhtemelen belirlenmiş başlatıcılara olan ihtiyacı ortadan kaldıran daha iyi bir tasarım sağlar. - Benim görüşüme göre, kümeler için sınıf içi başlatıcılar üzerindeki kısıtlamanın kaldırılması, belirlenmiş başlatıcılardan çok C ++ ethos ile uyumludur.
bames53

4
Bunun için C ++ değişimi işlev bağımsız değişkenleri olarak adlandırılabilir. Ancak şu an itibariyle, isim argümanları resmi olarak mevcut değil. Bunun bir teklifi için N4172 Adlandırılmış bağımsız değişkenlere bakın . Kodu daha az hataya yatkın hale getirir ve okumayı kolaylaştırır.
David Baird

89

15 Temmuz ' 17'de P0329R4 ,standart: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
Bu, aşağıdakiler için sınırlı destek sağlar:Atanmış Başlatıcılar. Bu sınırlama C.1.7 [diff.decl] .4 ile aşağıdaki şekilde açıklanmıştır:

struct A { int x, y; };
struct B { struct A a; };

C'de geçerli olan aşağıdaki Belirlenmiş Başlatma C ++ 'da kısıtlanmıştır:

  • struct A a = { .y = 1, .x = 2 } C ++ 'da geçersizdir çünkü göstericiler veri üyelerinin bildirim sırasında görünmelidir
  • int arr[3] = { [1] = 5 } C ++ 'da geçersiz çünkü dizi atanmış başlatma desteklenmiyor
  • struct B b = {.a.x = 0} C ++ 'da geçersizdir çünkü göstericiler yuvalanamaz
  • struct A c = {.x = 1, 2} C ++ 'da geçersizdir çünkü veri üyelerinin tümü veya hiçbiri belirteçler tarafından başlatılmalıdır

İçin ve daha önceki Boost , Atanmış Başlatıcılar için desteğe sahipti ve destek eklemek için çok sayıda teklif vardı.standart, örneğin: n4172 ve Daryle Walker'ın Başlatıcılara Atama Ekleme Önerisi . Öneriler,Visual C ++, gcc ve Clang'daki Belirlenmiş Başlatıcılar şunları iddia ediyor:

Değişikliklerin uygulanmasının nispeten kolay olacağına inanıyoruz

Ancak standart komite , bu tür önerileri defalarca reddederek şunları ifade eder:

EWG, önerilen yaklaşımla ilgili çeşitli sorunlar buldu ve birçok kez denendiği ve her başarısız olduğu için sorunu çözmeye çalışmanın mümkün olduğunu düşünmedi.

Ben Voigt'in yorumları , bu yaklaşımla ilgili aşılmaz sorunları görmeme yardımcı oldu; verilen:

struct X {
    int c;
    char a;
    float b;
};

Bu işlevler hangi sırayla çağrılırdı? : struct X foo = {.a = (char)f(), .b = g(), .c = h()}? Şaşırtıcı bir şekilde:

Herhangi bir başlatıcıdaki alt ifadelerin değerlendirme sırası belirsiz bir şekilde sıralanır [ 1 ]

(Visual C ++, gcc ve Clang, çağrıları bu sırayla yapacakları için üzerinde anlaşmaya varılmış bir davranışa sahip görünüyor :)

  1. h()
  2. f()
  3. g()

Ancak standardın belirsiz doğası, bu işlevlerin herhangi bir etkileşimi olsaydı, sonuçta ortaya çıkan program durumunun da belirsiz olacağı ve derleyicinin sizi uyarmayacağı anlamına gelir : Hatalı Çalışma Belirlenmiş Başlatıcılar Hakkında Uyarılmanın Bir Yolu Var mı?

gelmez sıkı başlatıcı listesi gereksinimleri 11.6.4 [dcl.init.list] 4:

Ayraçlı bir init listesinin başlatıcı listesi içinde, paket genişletmelerinden (17.5.3) kaynaklananlar da dahil olmak üzere başlatıcı yan tümceleri göründükleri sırayla değerlendirilir. Yani, belirli bir başlatıcı cümlesiyle ilişkili her değer hesaplaması ve yan etki, her değer hesaplamasından ve başlatıcı listesinin virgülle ayrılmış listesinde onu izleyen herhangi bir başlatıcı cümlesiyle ilişkili yan etkiden önce sıralanır.

Yani destek, bunun sırayla yürütülmesini gerektirecekti:

  1. f()
  2. g()
  3. h()

Önceki ile son derece uyumluluk uygulamalar.
Yukarıda tartışıldığı gibi, bu sorun, kabul edilen Atanan Başlatıcılar üzerindeki sınırlamalarla aşılmıştır.. Atanmış Başlatıcıların yürütme sırasını garanti eden standart bir davranış sağlarlar.


3
Elbette, bu kodda: struct X { int c; char a; float b; }; X x = { .a = f(), .b = g(), .c = h() };çağrı, h()ya f()da öncesinde gerçekleştirilir g(). Tanımı struct Xyakın değilse, bu çok şaşırtıcı olacaktır. Başlatıcı ifadelerinin yan etkisiz olması gerekmediğini unutmayın.
Ben Voigt

2
Tabii ki, bu yeni bir şey değil, ctor üye başlatmada zaten bu sorun var, ancak bu bir sınıf üyesi tanımında, bu yüzden sıkı birleşme sürpriz değil. Ve atanmış başlatıcılar, diğer üyelere, ctor üyesi başlatıcılarının yapabileceği şekilde başvuramaz.
Ben Voigt

2
@MattMcNabb: Hayır, daha aşırı değil. Ancak sınıf oluşturucuyu uygulayan geliştiricinin üye bildirim sırasını bilmesi beklenir. Oysa sınıfın tüketicisi tamamen farklı bir programcı olabilir. Bütün mesele üyelerin sırasına bakmadan başlatmaya izin vermek olduğundan, bu teklifte ölümcül bir kusur gibi görünüyor. Atanan başlatıcılar inşa edilmekte olan nesneye atıfta bulunamadığından, ilk izlenim, ilk olarak ilklendirme ifadelerinin atama sırasına göre değerlendirilebileceği ve ardından bildirim sırasına göre üye başlatmanın değerlendirilebileceğidir. Ama ...
Ben Voigt

2
@JonathanMee: Diğer soru şu yanıtı verdi ... C99 toplu başlatıcıları sıralı değil, bu nedenle belirlenmiş başlatıcıların sipariş edilmesi beklentisi yok. C ++ çaprazlı başlangıç ​​listeleri sıralanmıştır ve atanmış başlatıcılar için teklif potansiyel olarak şaşırtıcı bir sıra kullanır (hem tüm çaprazlı başlangıç ​​listeleri için kullanılan sözcük sıralaması hem de ctor başlatıcı için kullanılan üye sırası ile tutarlı olamazsınız -liste)
Ben Voigt

3
Jonathan: "c ++ desteği, bunun [...] sırayla yürütülmesini gerektirirdi, önceki c99 uygulamalarıyla uyumluluk bozuldu." Bunu anlamadım, üzgünüm. 1. Sipariş C99'da belirsizse , herhangi bir rastgele C ++ seçimi de dahil olmak üzere herhangi bir gerçek sipariş açıkça iyi olmalıdır. b) Deseni desteklememek. başlatıcıları zaten C99 uyumluluğunu daha da fazla bozuyor ...
Sz.

34

Biraz bilgisayar korsanlığı, bu yüzden sadece eğlence için paylaşın.

#define with(T, ...)\
    ([&]{ T ${}; __VA_ARGS__; return $; }())

Ve şu şekilde kullanın:

MyFunction(with(Params,
    $.Name = "Foo Bar",
    $.Age  = 18
));

şuna genişler:

MyFunction(([&] {
 Params ${};
 $.Name = "Foo Bar", $.Age = 18;
 return $;
}()));

Neat, $tür adında bir değişkenle bir lambda oluşturur Tve döndürmeden önce üyelerini doğrudan atarsınız. Şık. Bununla ilgili herhangi bir performans endişesi var mı merak ediyorum.
TankorSmash

1
Optimize edilmiş bir yapıda ne lambda ne de çağrılmasının izlerini görmezsiniz. Hepsi satır içi.
keebus

1
Bu cevabı kesinlikle çok seviyorum.
Seph Reed

6
Woah. $ 'In geçerli bir isim olduğunu bile bilmiyordum.
Chris Watts

Eski C derleyicileri tarafından desteklendi ve geriye dönük uyumluluk için destek kaldı.
keebus

22

Belirtilen başlatıcı şu anda C ++ 20 çalışma gövdesine dahil edilmiştir: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf, böylece sonunda onları görebiliriz!


3
Ancak sınırlandırılmış olduklarına dikkat edin: C ++ 'da, belirlenmiş başlatma desteği, C'deki karşılık gelen işlevselliğe kıyasla sınırlıdır. C ++' da, statik olmayan veri üyeleri için belirteçler bildirim sırasına göre belirtilmelidir, dizi öğeleri için belirteçler ve iç içe göstericiler değildir desteklenir ve belirlenmiş olan ve olmayan başlatıcılar aynı başlatıcı listesinde karıştırılamaz. Bu, özellikle numaralandırılmış bir arama tablosunu hala kolayca yapamayacağınız anlamına gelir .
Ruslan

@Ruslan: C ++ neden onları bu kadar kısıtladı acaba? Maddelerin değerlerinin değerlendirildiği ve / veya yapıya yazıldığı sıranın, ilklendirme listesinde öğelerin belirtildiği sırayla mı yoksa üyelerin yapıda göründüğü sırayla mı eşleştiği konusunda karışıklık olabileceğini anlıyorum, ancak bunun çözümü basitçe, başlatma ifadelerinin rastgele sırayla yürütüldüğünü ve nesnenin yaşam süresinin başlatma tamamlanana kadar başlamadığını söylemek &olacaktır ( operatör, nesnenin yaşam süresi boyunca sahip olacağı adresi döndürecektir ).
supercat

5

C ++ 11'de Bulunmayan İki Çekirdek C99 Özelliği , “Atanmış Başlatıcılar ve C ++” dan bahseder.

Potansiyel optimizasyonla ilgili 'atanmış başlatıcı' olduğunu düşünüyorum. Burada örnek olarak "gcc / g ++" 5.1 kullanıyorum.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>    
struct point {
    int x;
    int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

Derleme zamanında a_point.xsıfır olduğunu biliyorduk , bu yüzden bunun footek olarak optimize edilmesini bekleyebilirdik printf.

$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
   0x00000000004004f0 <+0>: sub    $0x8,%rsp
   0x00000000004004f4 <+4>: mov    $0x4005bc,%edi
   0x00000000004004f9 <+9>: xor    %eax,%eax
   0x00000000004004fb <+11>:    callq  0x4003a0 <printf@plt>
   0x0000000000400500 <+16>:    xor    %eax,%eax
   0x0000000000400502 <+18>:    add    $0x8,%rsp
   0x0000000000400506 <+22>:    retq   
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc:   "x == 0"

foox == 0yalnızca yazdırmak için optimize edilmiştir .

C ++ sürümü için,

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
    point(int _x,int _y):x(_x),y(_y){}
    int x;
    int y;
};
const struct point a_point(0,0);
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

Ve bu, optimize edilmiş montaj kodunun çıktısıdır.

g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>:    push   %rbx
0x00000000004005c1 <+1>:    mov    0x200489(%rip),%ebx        # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>:    test   %ebx,%ebx
0x00000000004005c9 <+9>:    je     0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>:   mov    $0x1,%ebx
0x00000000004005d0 <+16>:   mov    $0x4006a3,%edi
0x00000000004005d5 <+21>:   xor    %eax,%eax
0x00000000004005d7 <+23>:   callq  0x400460 <printf@plt>
0x00000000004005dc <+28>:   mov    %ebx,%eax
0x00000000004005de <+30>:   pop    %rbx
0x00000000004005df <+31>:   retq   
0x00000000004005e0 <+32>:   mov    $0x40069c,%edi
0x00000000004005e5 <+37>:   xor    %eax,%eax
0x00000000004005e7 <+39>:   callq  0x400460 <printf@plt>
0x00000000004005ec <+44>:   mov    %ebx,%eax
0x00000000004005ee <+46>:   pop    %rbx
0x00000000004005ef <+47>:   retq   

Bunun a_pointgerçekten bir derleme zamanı sabiti değeri olmadığını görebiliriz .


8
Şimdi lütfen dene constexpr point(int _x,int _y):x(_x),y(_y){}. clang ++ 'ın optimize edicisi, kodunuzdaki karşılaştırmayı da ortadan kaldırıyor gibi görünüyor. Yani, bu sadece bir QoI sorunu.
13'te dyp

Ayrıca dahili bir bağlantıya sahip olsaydı, a_point nesnesinin tamamının optimize edilmesini beklerdim. yani onu anonim isim alanına koyun ve ne olacağını görün. goo.gl/wNL0HC
Arvid

@dyp: Bir kurucu tanımlamak bile, yalnızca tür kontrolünüz altındaysa mümkündür. Bunu, örneğin struct addrinfoveya için yapamazsınız struct sockaddr_in, bu nedenle beyannamelerden ayrı görevlerle kalırsınız.
musiphil

2
@musiphil En azından C ++ 14'te, bu C-stili yapılar, atama kullanılarak yerel değişkenler olarak bir constexpr işlevinde düzgün bir şekilde kurulabilir ve sonra bu işlevden döndürülebilir. Ek olarak, amacım C ++ 'da yapıcının optimizasyona izin veren alternatif bir uygulamasını göstermek değil, ancak ilklendirme biçimi farklıysa derleyicinin bu optimizasyonu gerçekleştirmesinin mümkün olduğunu göstermekti. Derleyici "yeterince iyi" ise (yani, bu optimizasyon biçimini destekliyorsa), o zaman bir ctor veya belirlenmiş başlatıcılar veya başka bir şey kullanmanızla ilgisiz olmalıdır.
2017'de
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.