C ++ 11'deki yeni sözdizimi “= default”


136

Bunu neden hiç yapmam gerektiğini anlamıyorum:

struct S { 
    int a; 
    S(int aa) : a(aa) {} 
    S() = default; 
};

Neden sadece şunu söylemiyoruz:

S() {} // instead of S() = default;

bunun için neden yeni bir sözdizimi getirelim?


30
Nitpick: defaultyeni bir anahtar kelime değil, yalnızca ayrılmış bir anahtar kelimenin yeni bir kullanımıdır.


Mey be Bu soru size yardımcı olabilir.
FreeNickname

7
Diğer cevaplara ek olarak, '= varsayılan;' daha fazla kendi kendini belgelemektir.
Mark

Yanıtlar:


136

Varsayılan bir varsayılan kurucu, başlatma listesi ve boş bir bileşik ifadesi olmayan kullanıcı tanımlı bir varsayılan kurucu ile aynı olarak tanımlanır.

§12.1 / 6 [class.ctor] Varsayılan olarak silinen ve silinmemiş olarak tanımlanmayan varsayılan bir kurucu, sınıf türünde bir nesne oluşturmak için kullanıldığında veya ilk bildiriminden sonra açıkça varsayılan olarak ayarlandığında dolaylı olarak tanımlanır. Örtük olarak tanımlanmış varsayılan kurucu, ctor-başlatıcısı (12.6.2) ve boş bir bileşik deyimi olmadan o sınıf için kullanıcı tarafından yazılmış bir varsayılan kurucu tarafından gerçekleştirilecek sınıfın başlatma kümesini gerçekleştirir. [...]

Bununla birlikte, her iki kurucu da aynı şekilde davranacak olsa da, boş bir uygulamanın sağlanması sınıfın bazı özelliklerini etkiler. Kullanıcı tanımlı bir kurucuya, hiçbir şey yapmasa bile, türün toplu değil önemsiz olmasını sağlar . Sınıfınızın bir toplu veya önemsiz bir tür olmasını istiyorsanız (veya geçişlilik, bir POD türü), o zaman kullanmanız gerekir = default.

§8.5.1 / 1 [dcl.init.aggr] Toplam, kullanıcı tarafından sağlanan kurucuları olmayan bir dizi veya sınıftır, [ve ...]

§12.1 / 5 [class.ctor] Varsayılan bir kurucu, kullanıcı tarafından sağlanmadığında önemsizdir ve [...]

§9 / 6 [class] Önemsiz bir sınıf, önemsiz bir varsayılan yapıcıya sahip bir sınıftır ve [...]

Göstermek:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() { };
};

int main() {
    static_assert(std::is_trivial<X>::value, "X should be trivial");
    static_assert(std::is_pod<X>::value, "X should be POD");
    
    static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
    static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}

Ek olarak, bir kurucunun açık varsayılan olarak ayarlanması constexpr, örtük kurucu olsaydı bunu yapacaktır ve ayrıca örtük kurucunun sahip olduğu istisna şartnamesini de verecektir. Verdiğiniz durumda, örtük yapıcı constexpr(bir veri üyesini başlatılmadan bırakacağı için) olmazdı ve boş bir istisna belirtimi de olurdu, bu yüzden fark yoktur. Ancak evet, genel durumda constexpr, örtük yapıcıyla eşleşmesi için manuel olarak ve istisna belirtimini belirtebilirsiniz .

Kullanmak = defaultbazı tekdüzelik getirir, çünkü kopyala / taşı yapıcılar ve yıkıcılar ile de kullanılabilir. Örneğin, boş bir kopya oluşturucu, varsayılan bir kopya oluşturucu ile aynı olmaz (üyelerin üye olarak kopyalanmasını gerçekleştirir). Bu özel üye işlevlerinin her biri için = default(veya = delete) sözdizimini aynı şekilde kullanmak, niyetinizi açıkça belirterek kodunuzun okunmasını kolaylaştırır.


Neredeyse. 12.1 / 6: "Bu kullanıcı tarafından yazılan varsayılan kurucu bir constexprkurucu (7.1.5) gereksinimlerini karşılarsa , örtük olarak tanımlanmış varsayılan kurucu olur constexpr."
Casey

Aslında, 8.4.2 / 2 daha bilgilendiricidir: "Bir işlev ilk bildiriminde açıkça varsayılan olarak ayarlanmışsa, (a) constexprörtük bildirimin açık olacağı, dolaylı olarak aynı olduğu düşünülürse, istisna belirtimi, örtük olarak bildirilmiş gibi (15.4), ... "Bu özel durumda hiçbir fark yaratmaz, ancak genel olarak foo() = default;biraz avantajlıdır foo() {}.
Casey

2
Hiçbir fark olmadığını söylüyorsunuz ve farklılıkları açıklamaya devam edersiniz?

@hvd Bu durumda bir fark yoktur, çünkü örtülü bildirim olmaz constexpr(bir veri üyesi başlatılmadan bırakıldığı için) ve istisna özelliği tüm istisnalara izin verir. Bunu daha açık hale getireceğim.
Joseph Mansfield

2
Açıklama için teşekkürler. Bununla birlikte, hala bir fark var gibi görünüyor constexpr(burada bahsettiğiniz bir fark yaratmamalısınız): struct S1 { int m; S1() {} S1(int m) : m(m) {} }; struct S2 { int m; S2() = default; S2(int m) : m(m) {} }; constexpr S1 s1 {}; constexpr S2 s2 {};Sadece s1bir hata veriyor, değil s2. Hem clang hem de g ++ 'da.

10

Farkı gösterecek bir örneğim var:

#include <iostream>

using namespace std;
class A 
{
public:
    int x;
    A(){}
};

class B 
{
public:
    int x;
    B()=default;
};


int main() 
{ 
    int x = 5;
    new(&x)A(); // Call for empty constructor, which does nothing
    cout << x << endl;
    new(&x)B; // Call for default constructor
    cout << x << endl;
    new(&x)B(); // Call for default constructor + Value initialization
    cout << x << endl;
    return 0; 
} 

Çıktı:

5
5
0

Gördüğümüz gibi boş A () yapıcısı çağrısı üyeleri başlatmazken, B () yapar.


7
lütfen bu sözdizimini açıklar mısınız -> new (& x) A ();
Vencat

5
Bellekte, x değişkeninin adresinden (yeni bellek ayırma yerine) başlayan yeni bir nesne oluşturuyoruz. Bu sözdizimi, önceden ayrılmış bellekte nesne oluşturmak için kullanılır. Bizim durumumuzda olduğu gibi B boyutu int = yani, yeni (& x) A () x değişkeni yerine yeni nesne yaratacaktır.
Slavenskij

Açıklaman için teşekkürler.
Vencat

1
Gcc 8.3 ile farklı sonuçlar elde ediyorum: ideone.com/XouXux
Adam.Er8

C ++ 14 ile bile farklı sonuçlar alıyorum: ideone.com/CQphuT
Mayank Bhushan

9

n2210 bazı nedenler sağlar:

Varsayılanların yönetiminde birkaç sorun vardır:

  • Yapıcı tanımları birleştirilmiştir; herhangi bir kurucu bildirmek varsayılan kurucuyu bastırır.
  • Yıkıcı varsayılanı, açık bir tanım gerektiren polimorfik sınıflar için uygun değildir.
  • Bir kez varsayılan bastırıldığında, onu diriltmenin bir yolu yoktur.
  • Varsayılan uygulamalar genellikle manuel olarak belirtilen uygulamalardan daha verimlidir.
  • Varsayılan olmayan uygulamalar önemsizdir; bu, tür anlambilimini etkiler, örneğin POD olmayan bir tür oluşturur.
  • (Önemsiz) bir ikame beyan etmeden özel bir üye fonksiyonunu veya global operatörü yasaklamanın bir yolu yoktur.

type::type() = default;
type::type() { x = 3; }

Bazı durumlarda, sınıf gövdesi üye işlev tanımında bir değişiklik gerektirmeden değişebilir, çünkü varsayılan ek üyelerin bildirimi ile değişir.

Bkz . Üçlü Kural, C ++ 11 ile Beş Kural kurar mı? :

Diğer özel üye işlevlerini açıkça bildiren bir sınıf için taşıma yapıcısı ve taşıma atama işlecinin, bir taşıma yapıcısını veya taşıma işlemini açıkça bildiren bir sınıf için kopya oluşturucu ve kopya atama işlecinin oluşturulmayacağını unutmayın atama işleci ve açıkça tanımlanmış bir yıkıcı ve örtük olarak tanımlanmış bir kopya oluşturucu veya örtük olarak tanımlanmış bir kopya atama işleci olan bir sınıfın kullanımdan kaldırılmış olarak kabul edilmesi


1
Bunlar bir kurucuya karşı yapmaktan = defaultziyade genel olarak sahip olmanın nedenleridir . = default{ }
Joseph Mansfield

Çünkü @JosephMansfield Doğru ama {}zaten tanıtımı öncesinde dilinin bir özelliği olan =default, bu sebepler do ayrımı temel örtük (örn ima "diriltmek [Bastırılmış varsayılan] için hiçbir vasıta yoktur" {}olduğu değil , varsayılan eşdeğer ).
Kyle Strand

7

Bazı durumlarda anlambilim meselesi. Varsayılan kurucularla çok açık değildir, ancak derleyicinin oluşturduğu diğer üye işlevleriyle çok açık hale gelir.

Varsayılan kurucu için, boş bir gövdeye sahip herhangi bir varsayılan kurucuyu kullanmakla aynı önemsiz bir kurucu olmaya aday olarak kabul etmek mümkün olurdu =default. Sonuçta, eski boş varsayılan kurucuları yasal C ++ idi .

struct S { 
  int a; 
  S() {} // legal C++ 
};

Derleyicinin bu yapıcıyı önemsiz olarak algılayıp anlamadığı çoğu durumda optimizasyonların dışında (manuel veya derleyici olanlar) önemsizdir.

Ancak, boş işlev gövdelerini "varsayılan" olarak ele alma girişimi, diğer üye işlev türleri için tamamen bozulur. Kopya yapıcıyı düşünün:

struct S { 
  int a; 
  S() {}
  S(const S&) {} // legal, but semantically wrong
};

Yukarıdaki durumda, boş bir gövdeyle yazılmış kopya oluşturucu artık yanlıştır . Artık hiçbir şey kopyalamıyor. Bu, varsayılan kopya oluşturucu semantiğinden çok farklı bir anlambilim kümesidir. İstenen davranış bazı kodlar yazmanızı gerektirir:

struct S { 
  int a; 
  S() {}
  S(const S& src) : a(src.a) {} // fixed
};

Hatta bu basit dava ile, ancak kopya yapıcı kendini üretecek bir aynı olduğunu doğrulamak için derleyici için çok daha bir yük oluyor veya kopya yapıcı olduğunu görmek için önemsiz bir karşı (eşdeğer memcpytemelde, ). Derleyici, her üye başlangıç ​​ifadesini kontrol etmeli ve kaynağın karşılık gelen üyesine ve başka hiçbir şeye erişmenin ifadeyle özdeş olmasını sağlamalı, hiçbir üyenin önemsiz olmayan varsayılan yapı, vb. İle kalmadığından emin olmalıdır. derleyici, bu işlevin kendi oluşturulan sürümlerinin önemsiz olduğunu doğrulamak için kullanır.

O zaman, özellikle önemsiz olmayan durumlarda daha da tüylü olabilecek kopya atama operatörünü düşünün. Birçok sınıf için yazmak istemediğiniz bir ton kazan plakası, ancak yine de C ++ 03'te zorlanıyorsunuz:

struct T { 
  std::shared_ptr<int> b; 
  T(); // the usual definitions
  T(const T&);
  T& operator=(const T& src) {
    if (this != &src) // not actually needed for this simple example
      b = src.b; // non-trivial operation
    return *this;
};

Bu basit bir durumdur, ancak zaten böyle basit bir tür için yazmak zorunda kaldığınızdan daha fazla koddur T(özellikle karışıma hareketleri attığımızda). Boş gövdeye “varsayılanları doldurun” anlamına gelemeyiz, çünkü boş beden zaten tamamen geçerli ve açık bir anlamı vardır. Aslında, boş gövde "varsayılanları doldur" u belirtmek için kullanıldıysa, açıkça op olmayan bir kopya oluşturucu veya benzeri yapmanın bir yolu olmazdı.

Yine bir tutarlılık meselesi. Boş gövde "hiçbir şey yapma" anlamına gelir, ancak kopya yapıcılar gibi şeyler için gerçekten "hiçbir şey yapma" istemezsiniz, daha ziyade "bastırılmamışsa normalde yapacağınız her şeyi yaparsınız". Dolayısıyla =default. Bu var gerekli kopyalama / taşıma kurucular ve atama operatörleri gibi bastırılmış derleyici tarafından oluşturulan üye işlevlerini üstesinden gelmek için. Öyleyse varsayılan kurucu için de "açık" olur.

Boş gövdeli varsayılan kurucu yapmak ve önemsiz üye / taban kurucuları da =default, bazı durumlarda sadece eski kodu daha uygun hale getirmek için olduğu gibi önemsiz olarak kabul edilebilir , ancak çoğu düşük düzeyli kod önemsiz optimizasyonlar için varsayılan kurucular da önemsiz kopya kuruculara dayanır. Tüm eski kopya kurucularınızı gidip "düzeltmeniz" gerekecekse, tüm eski varsayılan kurucularınızı düzeltmek için çok fazla bir şey yoktur. Ayrıca =defaultniyetlerinizi belirtmek için açık bir şekilde kullanmak daha net ve daha açıktır .

Derleyici tarafından oluşturulan üye işlevlerinin, destek için de açık bir şekilde değişiklik yapmanız gerektiği konusunda yapacağı birkaç şey daha vardır. constexprVarsayılan kurucuları desteklemek bir örnektir. Zihinsel olarak kullanmak =default, işlevleri diğer tüm özel anahtar kelimelerle işaretlemek zorunda kalmaktan daha kolaydır =defaultve C ++ 11'in temalarından biri olan bu dili kolaylaştırır. Hala çok sayıda siğil ve geri uyumluluk tehlikesi var, ancak kullanım kolaylığı söz konusu olduğunda C ++ 03'ten ileriye doğru büyük bir adım olduğu açık.


Ben beklenen bir sorun vardı = defaultyapar a=0;ve değildi! Lehine bırakmak zorunda kaldım : a(0). = defaultTho hala performans konusunda ne kadar yararlı olduğu konusunda kafam karıştı. Sadece kullanmazsam bir yerde kırılır = defaultmı? Burada tüm cevapları okuma denedim bazı c ++ şeyler için yeniyim ve bunu anlamakta çok sorun yaşıyorum.
Kova Gücü

@AquariusPower: performans hakkında "sadece" değil, bazı durumlarda istisnalar ve diğer semantikler için de gereklidir. Yani, varsayılan bir operatör önemsiz olabilir, ancak varsayılan olmayan bir operatör hiçbir zaman önemsiz olamaz ve bazı kodlar önemsiz olmayan işlemlerin türlerini değiştirmek veya hatta reddetmek için meta programlama tekniklerini kullanır. Sizin a=0örnek çünkü ayrı (ilgili olsa) konu olan önemsiz türlerinin davranış olduğunu.
Sean Middleditch

sahip olmanın mümkün olduğu = defaultve hala verileceği aanlamına =0mı geliyor? bir şekilde? "nasıl bir yapıcı var = defaultve alanları düzgün bir şekilde başlatılacak vermek nasıl" gibi yeni bir soru oluşturabilir düşünüyor musunuz, btw bir structve değil sorunu vardı classve uygulama kullanmıyor olsa bile düzgün çalışıyor = default, yapabilirim iyi bir soru ise bu soruya minimal bir yapı ekleyin :)
Aquarius Power

1
@AquariusPower: statik olmayan veri üyesi başlatıcıları kullanabilirsiniz. Yapınızı şöyle yazın: struct { int a = 0; };Bir kurucuya ihtiyacınız olduğuna karar verirseniz, varsayılan olarak ayarlayabilirsiniz, ancak türün önemsiz olmayacağını (ki bu iyi değildir) unutmayın.
Sean Middleditch

2

Kullanımdan kaldırılması std::is_podve alternatifi nedeniyle std::is_trivial && std::is_standard_layout, @JosephMansfield'ın cevabındaki pasaj:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() {}
};

int main() {
    static_assert(std::is_trivial_v<X>, "X should be trivial");
    static_assert(std::is_standard_layout_v<X>, "X should be standard layout");

    static_assert(!std::is_trivial_v<Y>, "Y should not be trivial");
    static_assert(std::is_standard_layout_v<Y>, "Y should be standard layout");
}

Not Yhala standart düzeni olduğunu.

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.