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?
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?
Yanıtlar:
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 = default
bazı 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.
constexpr
kurucu (7.1.5) gereksinimlerini karşılarsa , örtük olarak tanımlanmış varsayılan kurucu olur constexpr
."
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() {}
.
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.
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 s1
bir hata veriyor, değil s2
. Hem clang hem de g ++ 'da.
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.
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
= default
ziyade genel olarak sahip olmanın nedenleridir . = default
{ }
{}
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 ).
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 memcpy
temelde, ). 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 =default
niyetlerinizi 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. constexpr
Varsayı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 =default
ve 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.
= default
yapar a=0;
ve değildi! Lehine bırakmak zorunda kaldım : a(0)
. = default
Tho hala performans konusunda ne kadar yararlı olduğu konusunda kafam karıştı. Sadece kullanmazsam bir yerde kırılır = default
mı? Burada tüm cevapları okuma denedim bazı c ++ şeyler için yeniyim ve bunu anlamakta çok sorun yaşıyorum.
a=0
örnek çünkü ayrı (ilgili olsa) konu olan önemsiz türlerinin davranış olduğunu.
= default
ve hala verileceği a
anlamına =0
mı geliyor? bir şekilde? "nasıl bir yapıcı var = default
ve alanları düzgün bir şekilde başlatılacak vermek nasıl" gibi yeni bir soru oluşturabilir düşünüyor musunuz, btw bir struct
ve değil sorunu vardı class
ve uygulama kullanmıyor olsa bile düzgün çalışıyor = default
, yapabilirim iyi bir soru ise bu soruya minimal bir yapı ekleyin :)
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.
Kullanımdan kaldırılması std::is_pod
ve 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 Y
hala standart düzeni olduğunu.
default
yeni bir anahtar kelime değil, yalnızca ayrılmış bir anahtar kelimenin yeni bir kullanımıdır.