Özel bir kurucu ne zaman özel bir kurucu değildir?


92

Diyelim ki bir türüm var ve varsayılan kurucusunu özel yapmak istiyorum. Ben şunu yazıyorum:

class C {
    C() = default;
};

int main() {
    C c;           // error: C::C() is private within this context (g++)
                   // error: calling a private constructor of class 'C' (clang++)
                   // error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC)
    auto c2 = C(); // error: as above
}

Harika.

Ama sonra, kurucunun sandığım kadar özel olmadığı ortaya çıktı:

class C {
    C() = default;
};

int main() {
    C c{};         // OK on all compilers
    auto c2 = C{}; // OK on all compilers
}    

Bu beni çok şaşırtıcı, beklenmedik ve açıkça istenmeyen bir davranış olarak görüyor. Bu neden uygun?


25
C c{};Hiçbir kurucu çağrılmadığı için toplu başlatma değil mi?
NathanOliver

5
@NathanOliver ne dedi. Kullanıcı tarafından sağlanan Cbir kurucunuz olmadığı için bir toplamanız da var.
Kerrek SB

5
@KerrekSB Aynı zamanda bir ctoru açıkça bildiren kullanıcının o ctor'u kullanıcı tarafından sağlanan yapmaması benim için oldukça şaşırtıcıydı.
Angew artık SO ile gurur duymuyor

1
@Angew Bu yüzden hepimiz buradayız :)
Barry

2
@Angew Eğer halka açık bir =defaultctor olsaydı, bu daha makul görünürdü. Ancak özel =defaultctor göz ardı edilmemesi gereken önemli bir şey gibi görünüyor. Dahası, class C { C(); } inline C::C()=default;oldukça farklı olmak biraz şaşırtıcı.
Yakk - Adam Nevraumont

Yanıtlar:


61

İşin püf noktası C ++ 14 8.4.2 / 5 [dcl.fct.def.default] 'da:

... Bir işlev, kullanıcı tarafından bildirilmişse ve ilk bildiriminde açıkça varsayılan değere sahip değilse veya silinmemişse , kullanıcı tarafından sağlanır . ...

Hangi demekse Cbireyin varsayılan yapıcı aslında değil açıkça ilk bildiriminde temerrüde çünkü, kullanıcı tarafından sağlanan. Bu nedenle, Ckullanıcı tarafından sağlanan oluşturuculara sahip değildir ve bu nedenle, 8.5.1 / 1 [dcl.init.aggr] başına bir toplamdır:

Bir toplama , kullanıcı tarafından sağlanan oluşturucular (12.1), özel veya korumalı statik olmayan veri üyeleri (Madde 11), temel sınıflar (Madde 10) ve sanal işlevler (10.3) içermeyen bir dizi veya sınıftır (Madde 9) ).


13
Aslında, küçük bir standart kusur: varsayılan ctor'un özel olduğu gerçeği aslında bu bağlamda göz ardı edilir.
Yakk - Adam Nevraumont

2
@Yakk Bunu yargılamak için yeterli hissetmiyorum. Yine de ctorun kullanıcı tarafından sağlanmadığına dair ifade çok kasıtlı görünüyor.
Angew artık SO ile gurur duymuyor

1
@Yakk: Evet ve hayır. Sınıfın herhangi bir veri üyesi varsa, bunları özel yapma şansınız olur. Veri üyeleri olmadan, bu durumun herhangi birini ciddi şekilde etkileyeceği çok az durum vardır.
Kerrek SB

2
@KerrekSB Sınıfı bir tür "erişim belirteci" kullanmaya çalışıyorsanız, örneğin sınıfın bir nesnesini kimin yaratabileceğine bağlı olarak kimin bir işlevi çağırabileceğini kontrol etmek önemlidir.
Angew artık SO ile gurur duymuyor

5
@Yakk Daha da ilginç olanı C{}, kurucu deleted olsa bile işe yarıyor .
Barry

56

Varsayılan kurucuyu çağırmıyorsunuz, toplu bir tür üzerinde toplu başlatma kullanıyorsunuz. İlk bildirildiği yerde varsayılan olduğu sürece, toplama türlerinin varsayılan bir kurucuya sahip olmasına izin verilir:

Kaynaktan [dcl.init.aggr] / 1 :

Bir toplama, bir dizi veya bir sınıftır (Madde [sınıf])

  • kullanıcı tarafından sağlanan kurucular yok ([class.ctor]) (temel sınıftan miras alınan ([namespace.udecl]) dahil),
  • özel veya korumalı statik olmayan veri üyeleri yok (Madde [class.access]),
  • sanal işlev yok ([class.virtual]) ve
  • sanal, özel veya korumalı temel sınıflar ([class.mi]) yoktur.

ve [dcl.fct.def.default] / 5 adresinden

Açıkça varsayılan işlevler ve örtük olarak bildirilen işlevler topluca varsayılan işlevler olarak adlandırılır ve uygulama, bunlar için örtük tanımlamalar sağlar ([class.ctor] [class.dtor], [class.copy]), bu da onları silinmiş olarak tanımlamak anlamına gelebilir . Bir işlev, kullanıcı tarafından bildirilmişse ve ilk bildiriminde açıkça varsayılan değere sahip değilse veya silinmemişse, kullanıcı tarafından sağlanır. Kullanıcı tarafından sağlanan açık bir şekilde temerrüde düşürülmüş bir işlev (yani, ilk bildiriminden sonra açıkça temerrüde düşürülmüştür), açıkça temerrüde düşürüldüğü noktada tanımlanır; böyle bir işlev örtük olarak silinmiş olarak tanımlanmışsa, program kötü biçimlendirilmiştir.[Not: Bir işlevin ilk bildiriminden sonra varsayılan olarak bildirilmesi, verimli bir yürütme ve kısa tanımlama sağlarken, gelişen bir kod tabanına kararlı bir ikili arabirimi etkinleştirebilir. - son not]

Bu nedenle, bir agrega için gereksinimlerimiz şunlardır:

  • halka açık olmayan üye yok
  • sanal işlev yok
  • sanal veya genel olmayan temel sınıf yok
  • devralınan veya başka şekilde kullanıcı tarafından sağlanan oluşturucular yoktur; bu, yalnızca şu yapıcılara izin verir:
    • dolaylı olarak beyan edilmiş veya
    • açıkça beyan edilmiş ve aynı zamanda temerrüde düşmüş olarak tanımlanmıştır.

C tüm bu gereksinimleri karşılar.

Doğal olarak, yalnızca boş bir varsayılan kurucu sağlayarak veya oluşturucuyu bildirdikten sonra varsayılan olarak tanımlayarak bu yanlış varsayılan yapım davranışından kurtulabilirsiniz:

class C {
    C(){}
};
// --or--
class C {
    C();
};
inline C::C() = default;

2
Bu cevabı Angew'den biraz daha çok beğeniyorum, ancak başlangıçta en fazla iki cümlelik bir özetin faydalı olacağını düşünüyorum.
PJTraill

7

Angew en ve jaggedSpire en cevaplar mükemmel ve uygulamak. Ve. Ve.

Ancak , işler biraz değişir ve OP'deki örnek artık derlenmeyecektir:

class C {
    C() = default;
};

C p;          // always error
auto q = C(); // always error
C r{};        // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20

İki cevabın da işaret ettiği gibi, son iki bildirimin işe yaramasının nedeni C, bir kümelenme olması ve bunun toplu başlatma olmasıdır. Bununla birlikte, P1008'in bir sonucu olarak ( OP'den çok farklı olmayan motive edici bir örnek kullanarak), C ++ 20'deki toplu değişikliklerin tanımı [dcl.init.aggr] / 1'e :

Bir toplama, bir dizi veya bir sınıftır ([sınıf])

  • bir kullanıcı tarafından beyan kalıtsal veya kurucular ([class.ctor]),
  • özel veya korumalı, statik olmayan doğrudan veri üyeleri ([class.access]) yok,
  • sanal işlev yok ([class.virtual]) ve
  • sanal, özel veya korumalı temel sınıflar ([class.mi]) yoktur.

Vurgu benim. Artık gereksinim, kullanıcı tarafından beyan edilen kurucular değildir, oysa eskiden (her iki kullanıcının da cevaplarında alıntı yaptığı ve C ++ 11 , C ++ 14 ve C ++ 17 için geçmişte görülebildiği için ) kullanıcı tarafından sağlanan kurucular yoktur. . İçin varsayılan kurucu Ckullanıcı tarafından bildirilir, ancak kullanıcı tarafından sağlanmamaktadır ve bu nedenle C ++ 20'de bir toplama olmaktan çıkar.


İşte toplu değişikliklerin başka bir açıklayıcı örneği:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

Btemel bir sınıfa sahip olduğu için C ++ 11 veya C ++ 14'te bir toplama değildi. Sonuç olarak, korumalı varsayılan kurucusuna B{}erişimi olan varsayılan kurucuyu (kullanıcı tarafından bildirilen ancak kullanıcı tarafından sağlanmayan) çağırır A.

C ++ 17'de, P0017'nin bir sonucu olarak , toplamalar temel sınıflara izin verecek şekilde genişletildi. BC ++ 17'de bir toplamadır, bu B{}, alt nesne dahil tüm alt nesneleri başlatması gereken toplu başlatma anlamına gelir A. Ancak Avarsayılan kurucusu korunduğu için ona erişimimiz yok, bu nedenle bu başlatma kötü biçimlendirilmiş.

C ++ 20'de, Bkullanıcı tarafından bildirilen kurucu nedeniyle, yine bir toplama olmaktan çıkar, bu nedenle B{}varsayılan kurucuyu çağırmaya geri döner ve bu yine iyi biçimlendirilmiş bir başlatmadır.

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.