C ++ Yapı Başlatma


279

Yapıları aşağıda belirtildiği gibi C ++ ile başlatmak mümkün müdür?

struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};
address temp_address =
    { .city = "Hamilton", .prov = "Ontario" };

Buradaki ve buradaki bağlantılar , bu stili yalnızca C'de kullanmanın mümkün olduğunu belirtiyor. Öyleyse, bu neden C ++ ile mümkün değildir? C ++ 'da uygulanmamasının altında yatan herhangi bir teknik neden var mı, yoksa bu stili kullanmak kötü bir uygulama mı? Yapımım büyük olduğundan ve bu stil bana hangi üyeye hangi değerin atandığı konusunda net bir okunabilirlik sağladığı için bu şekilde başlatma yöntemini kullanmayı seviyorum.

Aynı okunabilirliği sağlayabileceğimiz başka yollar varsa lütfen benimle paylaşın.

Bu soruyu göndermeden önce aşağıdaki bağlantılara başvurdum

  1. AIX için C / C ++
  2. C Değişken ile Yapı Başlatma
  3. C ++ etiketli statik yapı başlatma
  4. C ++ 11 Uygun Yapı Başlatma

20
Dünyanın kişisel görünümü: C ++ 'da bu nesne başlatma stiline ihtiyacınız yoktur çünkü bunun yerine bir yapıcı kullanmalısınız.
Philip Kendall

7
Evet bunu düşündüm, ama bir dizi büyük Yapım var. Bu şekilde kullanmak benim için kolay ve okunaklı olurdu. Daha iyi okunabilirlik sağlayan Oluşturucu'yu kullanarak başlatma yönteminiz / iyi uygulamanız var mı?
Dinesh PR

2
Boost parametre kitaplığını yapıcılarla birlikte düşündünüz mü? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy

18
Programlamayla ilgili değil: bu adres yalnızca ABD'de iyi çalışıyor. Fransa'da, bir "ilimiz" yok, dünyanın diğer bölgelerinde posta kodu yok, bir arkadaşın büyük annesi öyle küçük bir kasabada yaşıyor ki adresi "Bayan X, posta kodu küçük köy adı "(evet, sokak yok). Bu yüzden, pazar için geçerli bir adresin ne olduğunu dikkatlice düşünün;)
Matthieu M.7

5
@MatthieuM. ABD'de hiçbir eyalet yok (bu Kanadalı bir format olabilir mi?), Ancak sokakları adlandırmak için uğraşmayan eyaletler, bölgeler ve hatta küçük köyler var. Dolayısıyla adres uyumu konusu burada da geçerlidir.
Tim

Yanıtlar:


167

Her bir başlangıç ​​değeri değerinin ne olduğunu açıklığa kavuşturmak istiyorsanız, her biri için bir yorum olacak şekilde birden çok satıra bölün:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};

7
Şahsen bu stili seviyorum ve tavsiye ederim
Dinesh PR

30
Bunu yapmak ve aslında alanın kendisinden DAHA KESİNLİKLE erişmek için nokta gösterimini kullanmak arasındaki fark nedir, endişeniz bu ise herhangi bir yerden tasarruf ettiğiniz gibi değildir. Tutarlı ve bakım yapılabilir kod yazma söz konusu olduğunda gerçekten C ++ programcılarını alamıyorum, her zaman kodlarını öne çıkarmak için farklı bir şey yapmak istiyor gibi görünüyor, kod çözülmemesi gereken sorunu yansıtmak içindir tek başına bir deyim, güvenilirlik ve bakım kolaylığı hedefliyor.

5
@ user1043000 iyi, birincisi, bu durumda üyelerinizi koyduğunuz sıra çok önemlidir. Yapınızın ortasına bir alan eklerseniz, bu koda geri dönmeniz ve sert ve sıkıcı olan yeni başlatmanızın ekleneceği tam yeri aramanız gerekecektir. Nokta gösterimi ile, yeni başlatmanızı siparişle uğraşmadan listenin sonuna yerleştirebilirsiniz. Ve char*yapının üstündeki veya altındaki diğer üyelerden biriyle aynı türü (benzeri ) eklerseniz nokta gösterimi çok daha güvenlidir , çünkü bunları değiştirme riski yoktur.
Gui13

6
orip adlı kullanıcının yorumu. Veri yapısı tanımı değişirse ve hiç kimse başlatmaları aramayı düşünmezse veya hepsini bulamazsa veya düzenlerken bir hata yapmazsa, işler dağılacaktır.
Edward Falk

3
Çoğu (hepsi değilse de) POSIX yapılarının tanımlı bir sırası yoktur, sadece tanımlı üyeler vardır. (struct timeval){ .seconds = 0, .microseconds = 100 }her zaman yüz mikrosaniye olur, ancak timeval { 0, 100 }yüz SECONDS olabilir . Zor yoldan böyle bir şey bulmak istemezsin.
yyny

99

Sonra benim sorum : (C ++ yapılar için etiket tabanlı init uygulamıyor çünkü) hiçbir tatmin sonucu sonuçlandı, ben burada bulduğu hile aldı bir C ++ yapı üyeleri varsayılan olarak 0'a başlatıldı mı?

Sizin için bunu yapmak yeterli olacaktır:

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

Bu kesinlikle başlangıçta istediklerinize en yakın olanıdır (başlatmak istediğiniz alanlar hariç tüm alanları sıfırlayın).


10
Bu statik olarak eylemsiz nesneler için çalışmaz
user877329

5
static address temp_address = {};çalışacak. Daha sonra doldurmak çalışma zamanına bağlıdır, evet. Sizin için init yapan bir statik işlevi sağlayarak bu atlayabilir: static address temp_address = init_my_temp_address();.
Gui13

C ++ 11'de init_my_temp_addresslambda işlevi olabilir:static address temp_address = [] () { /* initialization code */ }();
dureuill

3
Kötü fikir, RAII prensibini ihlal ediyor.
hxpax

2
Gerçekten kötü bir fikir: bir üyeyi eklediğinizde, bir üyeyi addressoluşturan addressve artık yeni üyenizi başlatmayan tüm yerleri bilemezsiniz .
mystery_doctor


17

Alan tanımlayıcıları gerçekten de C başlatıcı sözdizimidir. C ++ 'da sadece alan adları olmadan değerleri doğru sırada verin. Ne yazık ki bu, hepsine vermeniz gerektiği anlamına gelir (aslında sıfır değerli alanları atlayabilirsiniz ve sonuç aynı olacaktır):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 

1
Evet, her zaman hizalı yapı başlatmayı kullanabilirsiniz.
texasbruce

3
Evet, şu anda yalnızca bu yöntemi kullanıyorum (Hizalanmış Yapı Başlatma). Ancak okunabilirliğin iyi olmadığını hissediyorum. Yapım büyük olduğu için başlatıcı çok fazla veriye sahip ve hangi üyeye hangi değerin atandığını izlemek benim için zor.
Dinesh PR

7
@ DineshP.R. Sonra bir kurucu yaz!
Bay Lister

2
@MrLister (veya herhangi biri) Belki de şu anda bir aptallık bulutuna sıkıştım, ama bir kurucunun nasıl daha iyi olacağını açıklamak isterim? Bana göre, bir başlatıcı listesine bir dizi siparişe bağlı isimsiz değer sağlama veya bir kurucuya bir dizi siparişe bağlı isimsiz değer sağlama arasında çok az fark var ...?
yano

1
@yano Dürüst olmak gerekirse, neden bir kurucunun sorunun cevabı olacağını düşündüğümü gerçekten hatırlamıyorum. Hatırlarsam, sana geri döneceğim.
Bay Lister

13

Bu özelliğe atanmış başlatıcılar denir . C99 standardına bir ektir. Ancak, bu özellik C ++ 11 dışında bırakılmıştır. C ++ Programlama Dili, 4. baskı, Bölüm 44.3.3.2'ye (C ++ Tarafından Kabul Edilmeyen C Özellikleri) göre:

C99'a birkaç ekleme (C89 ile karşılaştırıldığında) C ++ 'da kasıtlı olarak kabul edilmemiştir:

[1] Değişken uzunlukta diziler (VLA'lar); vektörü veya bir çeşit dinamik dizi kullan

[2] Belirlenmiş başlatıcılar; yapıcılar kullan

C99 dilbilgisi belirlenmiş başlatıcılara sahiptir [Bkz ISO / IEC 9899: 2011, N1570 Komite Taslağı - 12 Nisan 2011]

6.7.9 Başlatma

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designation_opt initializer
    initializer-list , designationopt initializer
designation:
    designator-list =
designator-list:
    designator
    designator-list designator
designator:
    [ constant-expression ]
    . identifier

Öte yandan, C ++ 11'in belirlenmiş başlatıcıları yoktur [Bkz ISO / IEC 14882: 2011, N3690 Komite Taslağı - 15 Mayıs 2013]

8.5 Başlatıcılar

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

Aynı efekti elde etmek için yapıcıları veya başlatıcı listelerini kullanın:


9

Sadece ctor ile başlatabilirsiniz:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};

9
Bu yalnızca tanımını kontrol ederseniz geçerlidir struct address. Ayrıca, POD tiplerinin genellikle kasıtlı olarak kurucu ve yıkıcıları yoktur.
user4815162342

7

Gui13'ün çözümünü tek bir başlatma bildiriminde bile paketleyebilirsiniz:

struct address {
                 int street_no;
                 char *street_name;
                 char *city;
                 char *prov;
                 char *postal_code;
               };


address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);

Yasal Uyarı: Bu stili önermiyorum


Bu hala tehlikelidir çünkü üye eklemenize izin verir addressve kod yine de orijinal beş üyeyi başlatan bir milyon yerle derlenir. Yapısal başlatmanın en iyi yanı, tüm üyeleriniz olabilmesidir constve sizi hepsini başlatmaya zorlayacaktır
mystery_doctor

7

Bu sorunun oldukça eski olduğunu biliyorum, ancak constexpr kullanarak ve köri kullanarak başlatma, başka bir yol buldum:

struct mp_struct_t {
    public:
        constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
        constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
        constexpr mp_struct_t another_member(int member) { return {member1, member,     member2}; }
        constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }

    int member1, member2, member3;
};

static mp_struct_t a_struct = mp_struct_t{1}
                           .another_member(2)
                           .yet_another_one(3);

Bu yöntem aynı zamanda küresel statik değişkenler ve hatta sürekli değişkenler için de geçerlidir. Tek dezavantajı kötü bakımdır: Başka bir üyenin bu yöntem kullanılarak başlatılması gerektiğinde, tüm üye başlatma yöntemleri değiştirilmelidir.


1
Bu kurucu model . Üye yöntemleri, her seferinde yeni bir yapı oluşturmak yerine değiştirilecek özelliğe bir başvuru döndürebilir
phuclv

6

Burada bir şey eksik olabilir, neden olmasın:

#include <cstdio>    
struct Group {
    int x;
    int y;
    const char* s;
};

int main() 
{  
  Group group {
    .x = 1, 
    .y = 2, 
    .s = "Hello it works"
  };
  printf("%d, %d, %s", group.x, group.y, group.s);
}

Yukarıdaki programı bir MinGW C ++ derleyicisi ve bir Arduino AVR C ++ derleyicisi ile derledim ve her ikisi de beklendiği gibi çalıştı. #İnclude <cstdio>
run_the_race

4
@ run_the_race, bu c ++ standardının belirli bir derleyicinin davranışının ne olabileceğini söylememesi ile ilgilidir. Ancak, bu özellik c ++ 20'de geliyor.
Jon

bu yalnızca yapı POD ise işe yarar. Bu yüzden bir kurucu eklerseniz derlemeyi durduracaktır.
oromoiluig

5

C ++ ile uygulanmaz. (ayrıca, char*dizeleri? Umarım değil).

Genellikle çok fazla parametreniz varsa oldukça ciddi bir kod kokusudur. Ama bunun yerine, neden sadece yapıyı değerlendirip sonra her üyeyi atamıyorsunuz?


6
"(ayrıca, char*dizeler? Umarım değil) - Bu bir C örneği.
Ed

C ++ 'da char * kullanamaz mıyız? Şu anda kullanıyorum ve çalışıyor (yanlış bir şey yapıyor olabilirim). Benim varsayımım derleyici sabit "Hamilton" ve "Ontario" dizeleri oluşturmak ve onların yapı üyelerine adres atamak olduğunu. Bunun yerine const char * kullanmak doğru olur mu?
Dinesh PR

8
Kullanabilirsiniz char*ama const char*çok daha güvenli ve herkes sadece std::stringdaha güvenilir olduğu için kullanıyor .
Köpek yavrusu

Tamam. "Aşağıda belirtildiği gibi" okuduğumda bunun bir yerden kopyalanmış bir örnek olduğunu varsaydım.
Ed

2

Orijinal yapı tanımını değiştirmeyi gerektirmeyen global değişkenler için bunu yapmanın yolunu buldum:

struct address {
             int street_no;
             char *street_name;
             char *city;
             char *prov;
             char *postal_code;
           };

ardından orijinal yapı türünden devralınan yeni bir tür değişkenini bildirin ve alanların başlatılması için yapıcıyı kullanın:

struct temp_address : address { temp_address() { 
    city = "Hamilton"; 
    prov = "Ontario"; 
} } temp_address;

C tarzı kadar zarif değil ama ...

Yerel bir değişken için, kurucunun başında ek bir memset (this, 0, sizeof (* this)) gerekir, bu yüzden açıkça daha kötü değildir ve @ gui13'ün cevabı daha uygundur.

('Temp_address' türünün 'temp_address' türünde bir değişken olduğunu unutmayın, ancak bu yeni tür 'adres' öğesinden miras alır ve 'adres' beklendiği her yerde kullanılabilir, bu yüzden sorun değil.)


1

Bugün benzer bir sorunla karşılaştım, burada test ettiğim bir işleve argüman olarak iletilecek test verileriyle doldurmak istediğim bir yapı var. Bu yapıların bir vektörüne sahip olmak istedim ve her bir yapıyı başlatmak için tek katmanlı bir yöntem arıyordum.

Sonunda, yapınıza birkaç soruda da önerildiğine inandığım bir yapıcı işlevi ile devam ettim.

Oluşturucuya yönelik argümanların, genel üye değişkenleriyle aynı isimlere sahip olması ve thisişaretçinin kullanılmasını gerektiren kötü bir uygulamadır . Daha iyi bir yol varsa, birisi düzenleme önerebilir.

typedef struct testdatum_s {
    public:
    std::string argument1;
    std::string argument2;
    std::string argument3;
    std::string argument4;
    int count;

    testdatum_s (
        std::string argument1,
        std::string argument2,
        std::string argument3,
        std::string argument4,
        int count)
    {
        this->rotation = argument1;
        this->tstamp = argument2;
        this->auth = argument3;
        this->answer = argument4;
        this->count = count;
    }

} testdatum;

Hangi benim test işlevinde bu gibi çeşitli argümanlar ile test ediliyor işlevini çağırmak için kullanılır:

std::vector<testdatum> testdata;

testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));

for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
    function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}

1

Mümkün, ancak başlattığınız yapı bir POD (düz eski veri) yapısı ise. Herhangi bir yöntem, yapıcı ve hatta varsayılan değerler içeremez.


1

C ++ 'da C-tarzı başlatıcılar, derleme zamanı ile sadece geçerli başlatmaların gerçekleştirilmesini sağlayabilen yapıcılar tarafından değiştirildi (başlatmadan sonra nesne üyeleri tutarlı).

Bu iyi bir uygulamadır, ancak bazen örneğinizdeki gibi bir ön başlatma kullanışlıdır. OOP bunu soyut sınıflarla veya yaratıcı tasarım desenleriyle çözer .

Kanımca, bu güvenli yolu kullanmak basitliği öldürür ve bazen güvenlik ödünleşimi çok pahalı olabilir, çünkü basit kodun bakım için kalıcı bir tasarıma ihtiyacı yoktur.

Alternatif bir çözüm olarak, neredeyse C stili gibi görünmesi için başlatmayı basitleştirmek için lambdas kullanarak makrolar tanımlamanızı öneririm:

struct address {
  int street_no;
  const char *street_name;
  const char *city;
  const char *prov;
  const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

ADDRESS makrosu,

[] { address _={}; /* definition... */ ; return _; }()

bu da lambda'yı yaratır ve çağırır. Makro parametreleri de virgülle ayrılmıştır, bu nedenle başlatıcıyı parantez içine almanız ve

address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));

Genelleştirilmiş makro başlatıcı da yazabilirsiniz

#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

ama sonra çağrı biraz daha az güzel

address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));

ancak genel INIT makrosunu kullanarak ADDRESS makrosunu kolayca tanımlayabilirsiniz

#define ADDRESS(x) INIT(address,x)


0

Bu gerçekten temiz cevaptan ilham aldık: ( https://stackoverflow.com/a/49572324/4808079 )

Lamba kapakları yapabilirsiniz:

// Nobody wants to remember the order of these things
struct SomeBigStruct {
  int min = 1;
  int mean = 3 ;
  int mode = 5;
  int max = 10;
  string name;
  string nickname;
  ... // the list goes on
}

.

class SomeClass {
  static const inline SomeBigStruct voiceAmps = []{
    ModulationTarget $ {};
    $.min = 0;  
    $.nickname = "Bobby";
    $.bloodtype = "O-";
    return $;
  }();
}

Veya çok süslü olmak istiyorsanız

#define DesignatedInit(T, ...)\
  []{ T ${}; __VA_ARGS__; return $; }()

class SomeClass {
  static const inline SomeBigStruct voiceAmps = DesignatedInit(
    ModulationTarget,
    $.min = 0,
    $.nickname = "Bobby",
    $.bloodtype = "O-",
  );
}

Bununla ilgili, çoğunlukla başlatılmamış üyelerle ilgili bazı dezavantajlar vardır. Bağlantılı cevapların söylediklerinden, test etmedim, ancak verimli bir şekilde derleniyor.

Genel olarak, bunun düzgün bir yaklaşım olduğunu düşünüyorum.

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.