C ++, bir const nesnesini varsayılan olarak oluşturmak için neden kullanıcı tarafından sağlanan bir varsayılan kurucu gerektirir?


99

C ++ standardı (bölüm 8.5) şunları söylüyor:

Bir program, const nitelikli T türündeki bir nesnenin varsayılan olarak başlatılmasını isterse, T, kullanıcı tarafından sağlanan varsayılan kurucuya sahip bir sınıf türü olacaktır.

Neden? Bu durumda kullanıcı tarafından sağlanan bir kurucunun neden gerekli olduğuna dair herhangi bir neden düşünemiyorum.

struct B{
  B():x(42){}
  int doSomeStuff() const{return x;}
  int x;
};

struct A{
  A(){}//other than "because the standard says so", why is this line required?

  B b;//not required for this example, just to illustrate
      //how this situation isn't totally useless
};

int main(){
  const A a;
}

2
Örneğinizde satır gerekli görünmüyor ( ideone.com/qqiXR'ye bakın ) çünkü siz beyan ettiniz ancak tanımlamadınız / başlatmadınız a, ancak gcc-4.3.4 bunu yaptığınızda bile kabul ediyor ( ideone.com/uHvFS'ye bakın )
Ray Toal

Yukarıdaki örnek hem bildirir hem de tanımlar a. Comeau, bir "const değişkeni" a "hatası üretir, bir başlatıcı gerektirir -" A "sınıfının, satırın yorumlanması durumunda, açıkça bildirilmiş varsayılan kurucu yoktur".
Karu


4
Bu, C ++ 11'de düzeltildi, yazabilirsiniz const A a{}:)
Howard Lovatt

Yanıtlar:


10

Bu bir kusur olarak kabul edildi (standardın tüm sürümlerine karşı) ve Core Working Group (CWG) Defect 253 tarafından çözüldü . Http://eel.is/c++draft/dcl.init#7'deki standart durumlar için yeni ifade

T'nin varsayılan olarak başlatılması, kullanıcı tarafından sağlanan T yapıcısını çağırırsa (bir temel sınıftan miras alınmamışsa) veya

  • T'nin her bir doğrudan değişken olmayan statik olmayan veri üyesi M bir varsayılan üye başlatıcısına sahiptir veya M, sınıf türü X (veya bunun dizisi) ise, X sabit varsayılan oluşturulabilirdir,
  • T, en az bir statik olmayan veri üyeli bir birleşim ise, tam olarak bir değişken üye varsayılan üye başlatıcısına sahiptir,
  • T bir birleşim değilse, en az bir statik olmayan veri üyesine (varsa) sahip her anonim birleşim üyesi için, tam olarak statik olmayan bir veri üyesinin varsayılan üye başlatıcısı vardır ve
  • T'nin potansiyel olarak oluşturulmuş her temel sınıfı, sabit varsayılan yapılandırılabilirdir.

Bir program, const nitelikli T türündeki bir nesnenin varsayılan olarak başlatılmasını çağırırsa, T, const-default-yapılandırılabilir sınıf türü veya bunun dizisi olacaktır.

Bu ifade, esasen bariz kodun çalıştığı anlamına gelir. Tüm üslerinizi ve üyelerinizi başlatırsanız, A const a;herhangi bir kurucuyu nasıl ya da hecelediğinizden bağımsız olarak söyleyebilirsiniz .

struct A {
};
A const a;

gcc bunu 4.6.4'ten beri kabul ediyor. clang bunu 3.9.0'dan beri kabul ediyor. Visual Studio da bunu kabul eder (en azından 2017'de, daha erken olup olmadığından emin değilim).


3
Ancak bu yine de struct A { int n; A() = default; }; const A a;izin verirken yasaklar struct B { int n; B() {} }; const B b;çünkü yeni ifade hala "kullanıcı tarafından sağlanan", "kullanıcı tarafından beyan edilmedi" diyor ve ben neden komitenin açıkça temerrüde düşmüş varsayılan kurucuları bu DR'den hariç tutmayı seçtiğini kafamı karıştırmaya bırakıyorum. İlklendirilmemiş üyelere sahip const nesneleri istiyorsak sınıflarımız önemsiz değildir.
Oktalist

1
İlginç, ama yine de karşılaştığım bir uç durum var. İle MyPODbir POD olmak struct, static MyPOD x;- sıfır başlatma esas alan değişken (ler) uygun bir şekilde ayarlamak için (? Doğru bir yani) - derler, ama static const MyPOD x;değildir. Olma ihtimali var mıdır o sabit alacak?
Joshua Green

66

Bunun nedeni, sınıfın kullanıcı tanımlı bir kurucusu yoksa, o zaman POD olabilmesi ve POD sınıfının varsayılan olarak başlatılmamasıdır. Öyleyse, başlatılmamış bir POD const nesnesi bildirirseniz, ne işe yarar? Bu yüzden, Standardın bu kuralı uyguladığını ve böylece nesnenin gerçekten faydalı olabileceğini düşünüyorum.

struct POD
{
  int i;
};

POD p1; //uninitialized - but don't worry we can assign some value later on!
p1.i = 10; //assign some value later on!

POD p2 = POD(); //initialized

const POD p3 = POD(); //initialized 

const POD p4; //uninitialized  - error - as we cannot change it later on!

Ancak sınıfı POD dışı yaparsanız:

struct nonPOD_A
{
    nonPOD_A() {} //this makes non-POD
};

nonPOD_A a1; //initialized 
const nonPOD_A a2; //initialized 

POD ve POD olmayanlar arasındaki farka dikkat edin.

Kullanıcı tanımlı kurucu, sınıfı POD dışı yapmanın bir yoludur. Bunu yapmanın birkaç yolu var.

struct nonPOD_B
{
    virtual void f() {} //virtual function make it non-POD
};

nonPOD_B b1; //initialized 
const nonPOD_B b2; //initialized 

NonPOD_B'nin kullanıcı tanımlı kurucuyu tanımlamadığına dikkat edin. Derleyin. Derleyecektir:

Ve sanal işlevi yorumlayın, ardından beklendiği gibi hata veriyor:


Sanırım geçidi yanlış anladın. Önce şunu söylüyor (§8.5 / 9):

Bir nesne için başlatıcı belirtilmemişse ve nesne (muhtemelen cv nitelikli) POD dışı sınıf tipindeyse (veya bunun dizisi), nesne varsayılan olarak başlatılacaktır; [...]

POD olmayan sınıftan muhtemelen cv nitelikli türden bahsediyor . Yani, POD olmayan nesne, herhangi bir başlatıcı belirtilmemişse varsayılan olarak başlatılacaktır. Ve varsayılan olarak başlatılan nedir? POD olmayanlar için spesifikasyon (§8.5 / 5),

T türünde bir nesneyi varsayılan olarak başlatmak şu anlama gelir:
- T, POD olmayan bir sınıf türüyse (madde 9), T için varsayılan kurucu çağrılır (ve T'nin erişilebilir varsayılan kurucusu yoksa başlatma kötü biçimlidir);

Kullanıcı tanımlı veya derleyici tarafından oluşturulmuş olsun, T'nin varsayılan yapıcısından bahseder .

Bunu anladıysanız, sonraki şartnamenin ne dediğini anlayın ((§8.5 / 9),

[...]; nesne const nitelikli türdeyse, temeldeki sınıf türünün kullanıcı tanımlı bir varsayılan kurucusu olmalıdır.

Bu metin ima Yani, programın kötü oluşacak eğer nesne taşımaktadır const nitelikli POD türü ve (POD başlatıldı default olmadığından) hiçbir başlatıcı orada belirtilir:

POD p1; //uninitialized - can be useful - hence allowed
const POD p2; //uninitialized - never useful  - hence not allowed - error

Bu arada, bu POD olmadığı için iyi bir şekilde derlenir ve varsayılan olarak başlatılabilir .


1
Son örneğinizin bir derleme hatası olduğuna inanıyorum - nonPOD_Bkullanıcı tarafından sağlanan bir varsayılan kurucu olmadığı için satıra const nonPOD_B b2izin verilmiyor.
Karu

1
Sınıfı POD olmayan yapmanın bir başka yolu da ona POD olmayan bir veri üyesi vermektir (örn. Sorudaki yapım B). Ancak bu durumda kullanıcı tarafından sağlanan varsayılan kurucu yine de gereklidir.
Karu

"Bir program, const nitelikli T türündeki bir nesnenin varsayılan olarak başlatılmasını isterse, T, kullanıcı tarafından sağlanan varsayılan kurucuya sahip bir sınıf türü olacaktır."
Karu

@Karu: Bunu okudum. Spesifikasyonda, constPOD olmayan nesnenin derleyici tarafından oluşturulan varsayılan kurucuyu çağırarak başlatılmasına izin veren başka pasajlar var gibi görünüyor .
Nawaz

2
İdeal bağlantılarınız kopmuş gibi görünüyor ve bu cevabın C ++ 11 / 14'e güncellenmesi harika olurdu çünkü §8.5 POD'dan hiç bahsetmiyor.
Oktalist

12

Benim açımdan saf spekülasyon, ancak diğer türlerin de benzer kısıtlamaları olduğunu düşünün:

int main()
{
    const int i; // invalid
}

Dolayısıyla, bu kural tutarlı olmakla kalmaz, aynı zamanda (yinelemeli olarak) birimselleştirilmiş const(alt) nesneleri önler :

struct X {
    int j;
};
struct A {
    int i;
    X x;
}

int main()
{
    const A a; // a.i and a.x.j in unitialized states!
}

Sorunun diğer tarafına gelince (varsayılan bir kurucuya sahip türler için izin verir), bence fikir, kullanıcı tarafından sağlanan bir varsayılan kurucuya sahip bir türün, inşaattan sonra her zaman mantıklı bir durumda olması gerektiğidir. Kuralların aşağıdakilere izin verdiğini unutmayın:

struct A {
    explicit
    A(int i): initialized(true), i(i) {} // valued constructor

    A(): initialized(false) {}

    bool initialized;
    int i;
};

const A a; // class invariant set up for the object
           // yet we didn't pay the cost of initializing a.i

O zaman belki 'en az bir üye, kullanıcı tarafından sağlanan varsayılan kurucuda mantıklı bir şekilde başlatılmalıdır' gibi bir kural formüle edebiliriz, ancak bu Murphy'ye karşı koruma sağlamaya çalışmak için çok fazla zaman harcanmıştır. C ++, programcıya belirli noktalarda güvenme eğilimindedir.


Ancak ekleyerek A(){}hata ortadan kalkar, dolayısıyla hiçbir şeyi engellemez. Kural yinelemeli olarak çalışmaz - X(){}bu örnek için asla gerekli değildir .
Karu

2
Eh, en azından bir kurucu eklemek için programcı zorlayarak, o soruna bir dakikalık bir düşünce vermek ve belki önemsiz olmayan bir biriyle gelip zorlanır
arne

O :) sabit - @Karu sadece yarı soruyu ele
Luc Danton

4
@arne: Tek sorun, yanlış programcı olması. Sınıfı somutlaştırmaya çalışan kişi, konuya istediği tüm düşünceyi verebilir, ancak sınıfı değiştiremeyebilir. Sınıf yazarı üyeler hakkında düşündü, hepsinin dolaylı varsayılan kurucu tarafından mantıklı bir şekilde başlatıldığını gördü, bu yüzden asla bir tane eklemedi.
Karu

3
Standardın bu bölümünden aldığım şey, "birisinin bir gün bir const örneği oluşturmak istemesi durumunda, her zaman POD olmayan türler için varsayılan bir kurucu bildir" dir. Bu biraz abartılı görünüyor.
Karu

3

C ++ 2018 Toplantısı'nda Timur Doumler'in konuşmasını izliyordum ve sonunda standardın neden yalnızca kullanıcı tarafından beyan edilen değil, kullanıcı tarafından sağlanan bir kurucu gerektirdiğini anladım. Değer başlatma kuralları ile ilgisi vardır.

İki sınıfları düşünün: ABir sahiptir kullanıcısının bildirdiği yapıcı, Bbir sahiptir kullanıcı tarafından sağlanan yapıcısı:

struct A {
    int x;
    A() = default;
};
struct B {
    int x;
    B() {}
};

İlk bakışta, bu iki kurucunun aynı şekilde davranacağını düşünebilirsiniz. Ancak, değer başlatmanın nasıl farklı davrandığını, yalnızca varsayılan başlatmanın aynı şekilde davrandığını görün:

  • A a;varsayılan başlatmadır: üye int xbaşlatılmamıştır.
  • B b;varsayılan başlatmadır: üye int xbaşlatılmamıştır.
  • A a{};değeri başlatılması olduğu: üye int xolan sıfır başlatılır .
  • B b{};değer başlatmadır: üye int xbaşlatılmamıştır.

Şimdi şunu eklediğimizde ne olacağını görün const:

  • const A a;Varsayılan başlatma şudur: Bu edilir kötü şekillendirilmiş nedeniyle söz konusu alıntılanan kuralın.
  • const B b;varsayılan başlatmadır: üye int xbaşlatılmamıştır.
  • const A a{};değeri başlatılması olduğu: üye int xolan sıfır başlatılır .
  • const B b{};değer başlatmadır: üye int xbaşlatılmamıştır.

İlklendirilmemiş bir constskaler (örneğin int xüye) işe yaramaz: ona yazmak kötü biçimlidir (çünkü öyle const) ve ondan okumak UB'dir (çünkü belirsiz bir değeri vardır). Sizi zorlayarak, böyle bir şey oluşturmasını bu kural önler So ya Başlatıcı bir ekleme veya bir kullanıcı tarafından sağlanan yapıcı ekleyerek tehlikeli davranışlara opt-.

[[uninitialized]]Bir nesneyi kasıtlı olarak başlatmadığınızda derleyiciye söylemek gibi bir niteliğe sahip olmanın güzel olacağını düşünüyorum . Öyleyse, bu köşe vakasını aşmak için sınıfımızı önemsiz bir şekilde varsayılan olarak inşa edilebilir yapmak zorunda kalmayız. Bu özellik aslında önerilmiştir , ancak diğer tüm standart nitelikler gibi, herhangi bir normatif davranışı zorunlu kılmaz, yalnızca derleyiciye bir ipucu olur.


1

Tebrikler, constherhangi bir başlatıcının anlamlı olması için bildirim için kullanıcı tanımlı herhangi bir kurucuya ihtiyaç duyulmayan bir durum icat ettiniz .

Şimdi, davanızı kapsayan, ancak yine de yasadışı olması gereken davaları yasa dışı kılan, kuralın makul bir yeniden ifade edilmesini bulabilir misiniz? 5 veya 6 paragraftan az mı? Her durumda nasıl uygulanması gerektiği kolay ve açık mı?

Oluşturduğunuz bildirimin mantıklı olmasını sağlayan bir kural bulmanın gerçekten zor olduğunu ve kuralın, kodu okurken insanlara mantıklı bir şekilde uygulanmasını sağlamanın daha da zor olduğunu düşünüyorum. Çoğu durumda yapılması doğru olan biraz kısıtlayıcı bir kuralı, anlaşılması ve uygulanması zor olan çok incelikli ve karmaşık bir kurala tercih ederim.

Soru şu ki, kuralın daha karmaşık olması için ikna edici bir neden var mı? Aksi takdirde yazılması veya anlaşılması çok zor olacak, kural daha karmaşıksa çok daha basit bir şekilde yazılabilecek bir kod var mı?


1
İşte benim önerdiğim sözler: "Bir program, const özellikli T tipi bir nesnenin varsayılan olarak başlatılmasını isterse, T, POD olmayan bir sınıf türü olacaktır." Bu, const POD x;tıpkı yasa dışı olduğu gibi const int x;yasa dışı olur (bu mantıklıdır, çünkü bu bir POD için işe yaramaz), ancak const NonPOD x;yasaldır (bu mantıklıdır, çünkü yararlı kurucular / yıkıcılar içeren alt nesneleri olabilir veya yararlı bir kurucu / yıkıcıya sahip olabilir) .
Karu

@Karu - Bu ifade işe yarayabilir. RFC standartlarına alışkınım ve bu yüzden 'T olmalı' nın 'T olmalı' olarak okunması gerektiğini hissediyorum. Ama evet, bu işe yarayabilir.
Omnifarious

@Karu - struct NonPod hakkında {int i; sanal boşluk f () {}}? Const yapmak mantıklı değil NonPod x; yasal.
gruzovator

1
@gruzovator Boş bir kullanıcı tanımlı varsayılan kurucunuz olsaydı daha mantıklı olur muydu? Benim önerim sadece standardın anlamsız bir gerekliliğini ortadan kaldırmaya çalışıyor; onunla veya onsuz, anlamsız kod yazmanın hala sonsuz sayıda yolu vardır.
Karu

1
@Karu sana katılıyorum. Standarttaki bu kural nedeniyle, kullanıcı tanımlı boş kurucuya sahip olması gereken birçok sınıf vardır . Gcc davranışını seviyorum. Örneğin struct NonPod { std::string s; }; const NonPod x;, NonPod olduğunda bir hata veriyorstruct NonPod { int i; std::string s; }; const NonPod x;
gruzovator
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.