Toplam başlatma elemanlarının ihmal edilmesini önlemek mümkün müdür?


43

Aynı türden birçok üyeye sahip bir yapım var, böyle

struct VariablePointers {
   VariablePtr active;
   VariablePtr wasactive;
   VariablePtr filename;
};

Sorun şudur ki, yapı üyelerinden birini (örneğin wasactive) başlatmayı unutursam , şöyle:

VariablePointers{activePtr, filename}

Derleyici bundan şikayet etmeyecek, ancak kısmen başlatılan bir nesnem olacak. Bu tür bir hatayı nasıl önleyebilirim? Bir yapıcı ekleyebilirim, ancak değişken listesini iki kez çoğaltır, bu yüzden tüm bunları üç kez yazmak zorundayım!

C ++ 11 için bir çözüm varsa lütfen şu anda C ++ 11 cevaplarını ekleyin (şu anda bu sürümle sınırlıyım). Daha yeni dil standartları da kabul edilir!


6
Bir yapıcı yazmak o kadar da korkunç değil. Çok fazla üyeniz olmadığı sürece, bu durumda belki yeniden düzenleme yapmak mümkündür.
Gonen I

1
@Someprogrammerdude Sanırım hatanın yanlışlıkla bir başlangıç ​​değerini atlayabileceğiniz anlamına geldiğini düşünüyorum
Gonen I

2
Dizi / vektörün nasıl yardımcı olduğunu biliyorsanız @theWiseBro bir yanıt göndermelisiniz. Bu kadar açık değil, görmüyorum
idclev 463035818

2
@Someprogrammerdude Ama bu bir uyarı mı? VS2019 ile göremiyorum.
acraig5075

8
Bir -Wmissing-field-initializersderleme bayrağı var.
Ron

Yanıtlar:


42

Gerekli bir başlatıcı eksikse bir bağlayıcı hatasını tetikleyen bir hile:

struct init_required_t {
    template <class T>
    operator T() const; // Left undefined
} static const init_required;

Kullanımı:

struct Foo {
    int bar = init_required;
};

int main() {
    Foo f;
}

Sonuç:

/tmp/ccxwN7Pn.o: In function `Foo::Foo()':
prog.cc:(.text._ZN3FooC2Ev[_ZN3FooC5Ev]+0x12): undefined reference to `init_required_t::operator int<int>() const'
collect2: error: ld returned 1 exit status

Uyarılar:

  • C ++ 14'ten önce, bu Foobir araya gelmeyi tamamen engeller .
  • Bu teknik olarak tanımlanmamış davranışa (ODR ihlali) dayanır, ancak herhangi bir aklı başında platformda çalışmalıdır.

Dönüştürme işlecini silebilirsiniz ve derleyici hatasıdır.
jrok

@jrok evet, ancak Foooperatörü hiç aramasanız bile, beyan edildiği anda bir tanesidir .
Quentin

2
@jrok Fakat başlatma sağlansa bile derlenmez. godbolt.org/z/yHZNq_ Ek: MSVC için tarif ettiğiniz gibi çalışır: godbolt.org/z/uQSvDa Bu bir hata mı?
n314159

Elbette, aptal bana.
jrok

6
Ne yazık ki, bu hile C ++ 11 ile çalışmıyor, çünkü o zaman bir toplu olmayan olacak :( C ++ 11 etiketini kaldırdım, bu yüzden cevabınız da geçerli (lütfen silmeyin), ancak mümkünse bir C ++ 11 çözümü tercih edilir
Johannes Schaub - litb

22

Clang ve gcc -Werror=missing-field-initializersiçin, eksik alan başlatıcılarındaki uyarıyı bir hataya dönüştüren bir derleme yapabilirsiniz . Godbolt

Düzenleme: MSVC için, hiçbir düzeyde bile uyarı yayılan görünüyor /Wall, bu yüzden bu derleyici ile eksik başlatıcıları uyarmak mümkün olduğunu sanmıyorum. Godbolt


7

Zarif ve kullanışlı bir çözüm değil, sanırım ... ancak C ++ 11 ile de çalışmalı ve derleme zamanı (bağlantı zamanı değil) hatası vermelidir.

Fikir, yapınıza varsayılan konumlandırması olmayan bir türün son konumuna ek bir üye eklemektir (ve bir tür değeriyle VariablePtr(veya önceki değerlerin türü ne olursa olsun) başlatılamaz.

Örnek olarak

struct bar
 {
   bar () = delete;

   template <typename T> 
   bar (T const &) = delete;

   bar (int) 
    { }
 };

struct foo
 {
   char a;
   char b;
   char c;

   bar sentinel;
 };

Bu şekilde, toplam başlatma listenizdeki tüm öğeleri eklemek zorunda kalırsınız, son değeri ( sentinelörnekte bir tamsayı) açıkça başlatmak için değer dahil ettiniz veya "bar 'silinen yapıcısına çağrı" hatası alıyorsunuz.

Yani

foo f1 {'a', 'b', 'c', 1};

derlemek ve

foo f2 {'a', 'b'};  // ERROR

yapmaz.

Maalesef

foo f3 {'a', 'b', 'c'};  // ERROR

derlemez.

-- DÜZENLE --

MSalters'ın işaret ettiği gibi (teşekkürler) orijinal örneğimde bir kusur (başka bir kusur) var: bir bardeğer bir chardeğerle (dönüştürülebilir int) başlatılabilir, bu nedenle aşağıdaki başlatma

foo f4 {'a', 'b', 'c', 'd'};

ve bu oldukça kafa karıştırıcı olabilir.

Bu sorunu önlemek için, aşağıdaki silinmiş şablon yapıcısını ekledim

 template <typename T> 
 bar (T const &) = delete;

bu nedenle, değer silinmiş şablon yapıcısı tarafından kesildiği için önceki f4bildirim bir derleme hatası verird


Teşekkürler, bu güzel! Bahsettiğiniz gibi mükemmel değil ve aynı zamanda foo f;derlemede başarısız oluyor, ancak belki de bu hile ile ilgili bir kusurdan daha fazla bir özelliktir. Bundan daha iyi bir teklif yoksa kabul eder.
Johannes Schaub - litb

1
Bar yapıcı okunabilirlik için init_list_end gibi bir şey denilen bir const iç içe sınıf üyesi kabul yapar
Gonen I

@GonenI - okunabilirlik için bir değeri kabul edebilir enumve init_list_end(o sadece list_end) bunun bir değerini adlandırabilirsiniz enum; ancak okunabilirlik çok fazla daktilo ekliyor, bu nedenle, ek değerin bu cevabın zayıf noktası olduğu göz önüne alındığında, bunun iyi bir fikir olup olmadığını bilmiyorum.
max66

Belki constexpr static int eol = 0;başlığına benzer bir şey ekleyin bar. test{a, b, c, eol}benim için oldukça okunabilir görünüyor.
n314159

@ n314159 - iyi ... ol bar::eol; neredeyse bir enumdeğer vermek gibi; ama bunun önemli olduğunu düşünmüyorum: cevabın özü "yapınıza son konumda, varsayılan başlatma olmadan bir tür ek bir üye eklemek" tir; barparçası çözüm çalıştığını göstermek için sadece önemsiz bir örnektir; "varsayılan başlatma olmadan tam" türü koşullara (IMHO) bağlı olmalıdır.
max66

4

İçin CppCoreCheck tam kontrol için bir kural var, hepsi üyeleri başlatıldı edilmiş ve bu bir hata içine uyarısından açılabilir eğer - tabii genellikle program genişliğindedir.

Güncelleme:

Kontrol etmek istediğiniz kural yazım güvenliğinin bir parçasıdır Type.6:

Tür 6: Her zaman bir üye değişkeni başlat: her zaman başlat, muhtemelen varsayılan kurucular veya varsayılan üye başlatıcılar kullanarak.


2

En basit yol, üyelerin türüne arg olmayan bir kurucu vermemek:

struct B
{
    B(int x) {}
};
struct A
{
    B a;
    B b;
    B c;
};

int main() {

        // A a1{ 1, 2 }; // will not compile 
        A a1{ 1, 2, 3 }; // will compile 

Başka bir seçenek: Üyeleriniz const ise &, hepsini başlatmanız gerekir:

struct A {    const int& x;    const int& y;    const int& z; };

int main() {

//A a1{ 1,2 };  // will not compile 
A a2{ 1,2, 3 }; // compiles OK

Bir kukla const ve üye ile yaşayabiliyorsanız, bunu @ max66'nın bir sentinel fikri ile birleştirebilirsiniz.

struct end_of_init_list {};

struct A {
    int x;
    int y;
    int z;
    const end_of_init_list& dummy;
};

    int main() {

    //A a1{ 1,2 };  // will not compile
    //A a2{ 1,2, 3 }; // will not compile
    A a3{ 1,2, 3,end_of_init_list() }; // will compile

Cppreference sitesinden https://en.cppreference.com/w/cpp/language/aggregate_initialization

Başlatıcı cümlelerinin sayısı üye sayısından azsa veya başlatıcı listesi tamamen boşsa, kalan üyeler değerle başlatılır. Bir başvuru türünün bir üyesi kalan bu üyelerden biriyse, program kötü biçimlendirilir.

Başka bir seçenek de max66'nın nöbetçi fikrini almak ve okunabilirlik için biraz sözdizimsel şeker eklemek

struct init_list_guard
{
    struct ender {

    } static const end;
    init_list_guard() = delete;

    init_list_guard(ender e){ }
};

struct A
{
    char a;
    char b;
    char c;

    init_list_guard guard;
};

int main() {
   // A a1{ 1, 2 }; // will not compile 
   // A a2{ 1, init_list_guard::end }; // will not compile 
   A a3{ 1,2,3,init_list_guard::end }; // compiles OK

Ne yazık ki, bu Ataşınmaz hale getirir ve kopya semantiğini değiştirir ( Atabiri caizse, artık değerlerin toplamı değildir) :(
Johannes Schaub - litb

@ JohannesSchaub-litb Tamam. Düzenlenmiş cevabımda bu fikre ne dersin?
Gönen Ben

@ JohannesSchaub-litb: eşit derecede önemli olarak, ilk versiyon üyeleri işaretçiler yaparak bir miktar dolaylılık ekler. Daha da önemlisi, bir şeye referans olması gerekir ve 1,2,3nesneler, işlev sona erdiğinde kapsam dışı kalan otomatik depolamada etkili bir şekilde yereldir. Ve 64 bit işaretçileri olan bir sistemde (x86-64 gibi) 3 yerine sizeof (A) 24 yapar.
Peter Cordes

Kukla bir referans, boyutu 3 ile 16 bayt arasında artırır (işaretçi (başvuru) üyesinin + işaretçinin kendisinin hizalanması için dolgu.) Referansı asla kullanmadığınız sürece, dışarıda bırakılan bir nesneyi işaret ediyorsa, muhtemelen iyi olur dürbün. Kesinlikle optimize değil endişe ediyorum ve etrafında kopyalamak kesinlikle olmaz. (Boş bir sınıfın büyüklüğü dışında optimize etme şansı daha yüksektir, bu nedenle buradaki üçüncü seçenek en az kötüdür, ancak yine de en azından bazı ABI'larda her nesnede alan maliyeti vardır. Bazı durumlarda optimizasyon.)
Peter Cordes
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.