Inisialisasi Struktur C ++


279

Apakah mungkin untuk menginisialisasi struct di C ++ seperti yang ditunjukkan di bawah ini

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

Tautan di sini dan di sini menyebutkan bahwa adalah mungkin untuk menggunakan gaya ini hanya dalam C. Jika demikian mengapa ini tidak mungkin di C ++? Apakah ada alasan teknis yang mendasari mengapa itu tidak diimplementasikan dalam C ++, atau apakah itu praktik yang buruk untuk menggunakan gaya ini. Saya suka menggunakan cara inisialisasi ini karena struct saya besar dan gaya ini memberi saya keterbacaan yang jelas tentang nilai apa yang diberikan kepada anggota mana.

Silakan bagikan dengan saya jika ada cara lain agar kami dapat mencapai keterbacaan yang sama.

Saya telah merujuk tautan berikut sebelum memposting pertanyaan ini

  1. C / C ++ untuk AIX
  2. Inisialisasi Struktur C dengan Variabel
  3. Inisialisasi struktur statis dengan tag dalam C ++
  4. C ++ 11 Inisialisasi Struktur yang Tepat

20
Pandangan pribadi tentang dunia: Anda tidak perlu gaya inisialisasi objek ini dalam C ++ karena Anda harus menggunakan konstruktor.
Philip Kendall

7
Ya saya memikirkan hal itu, tetapi saya memiliki susunan Struktur besar. Akan mudah dan mudah dibaca bagi saya untuk menggunakan cara ini. Apakah Anda memiliki gaya / praktik inisialisasi yang baik menggunakan Konstruktor yang memberikan keterbacaan yang lebih baik juga.
Dinesh PR

2
Sudahkah Anda mempertimbangkan pustaka parameter boost, dikombinasikan dengan konstruktor? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy

18
Tidak terkait pemrograman: alamat ini hanya berfungsi di AS. Di Prancis, kami tidak memiliki "provinsi", di bagian lain dunia, tidak ada kode pos, seorang nenek dari seorang teman tinggal di desa kecil sehingga alamatnya adalah "Ms X, kode pos" nama desa kecil "(ya, tidak ada jalan). Jadi pertimbangkan baik-baik alamat yang valid untuk pasar tempat Anda akan menerapkan ini;)
Matthieu M.

5
@ MatthieuM. Tidak ada provinsi di AS (ini mungkin format Kanada?), Tetapi ada negara bagian, teritori, dan bahkan desa-desa kecil yang tidak mau repot menyebut jalan. Jadi masalah kesesuaian alamat berlaku bahkan di sini.
Tim

Jawaban:


167

Jika Anda ingin memperjelas apa nilai masing-masing penginisialisasi, cukup bagi menjadi beberapa baris, dengan komentar pada masing-masing:

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

7
Saya pribadi suka dan merekomendasikan gaya ini
Dinesh PR

30
Apa perbedaan antara melakukan itu, dan benar-benar menggunakan notasi titik untuk mengakses LEBIH TEPAT bidang itu sendiri, itu tidak seperti Anda menghemat ruang jika itu yang menjadi perhatian. Saya benar-benar tidak mendapatkan programmer C ++ ketika datang untuk menjadi konsisten dan menulis kode yang dapat dipelihara, mereka tampaknya selalu ingin melakukan sesuatu yang berbeda untuk membuat kode mereka menonjol, kode ini dimaksudkan untuk mencerminkan masalah yang sedang dipecahkan seharusnya tidak sebuah idiom sendiri, bertujuan untuk keandalan dan kemudahan perawatan.

5
@ user1043000 baik, untuk satu, dalam hal ini urutan di mana Anda menempatkan anggota Anda sangat penting Jika Anda menambahkan bidang di tengah struktur Anda, Anda harus kembali ke kode ini dan mencari tempat yang tepat untuk memasukkan inisialisasi baru Anda, yang sulit dan membosankan. Dengan notasi titik, Anda cukup meletakkan inisialisasi baru di akhir daftar tanpa mengganggu urutannya. Dan notasi titik lebih aman jika Anda menambahkan tipe yang sama (seperti char*) sebagai salah satu anggota lain di atas atau di bawah dalam struktur, karena tidak ada risiko menukar mereka.
Gui13

6
komentar orip. Jika definisi struktur data berubah, dan tidak ada yang berpikir untuk mencari inisialisasi, atau tidak dapat menemukan semuanya, atau membuat kesalahan dengan mengeditnya, semuanya akan berantakan.
Edward Falk

3
Sebagian besar (jika tidak semua) struct POSIX tidak memiliki urutan yang ditentukan, hanya anggota yang ditentukan. (struct timeval){ .seconds = 0, .microseconds = 100 }akan selalu menjadi seratus mikrodetik, tetapi timeval { 0, 100 }mungkin seratus DETIK . Anda tidak ingin menemukan hal seperti itu dengan cara yang sulit.
yyny

99

Setelah pertanyaan saya menghasilkan hasil yang tidak memuaskan (karena C ++ tidak mengimplementasikan init berbasis tag untuk struktur), saya mengambil trik yang saya temukan di sini: Apakah anggota struct C ++ diinisialisasi ke 0 secara default?

Bagi Anda itu sama saja dengan melakukan itu:

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

Ini tentunya yang paling dekat dengan apa yang Anda inginkan pada awalnya (nol semua bidang kecuali yang ingin Anda inisialisasi).


10
Ini tidak berfungsi untuk objek yang tidak diinintis secara statis
user877329

5
static address temp_address = {};akan bekerja. Mengisi itu setelahnya sudah sampai ke runtime, ya. Anda dapat melewati ini dengan menyediakan fungsi statis yang melakukan init untuk Anda: static address temp_address = init_my_temp_address();.
Gui13

Dalam C ++ 11, init_my_temp_addressbisa menjadi fungsi lambda:static address temp_address = [] () { /* initialization code */ }();
dureuill

3
Gagasan buruk, itu melanggar prinsip RAII.
hxpax

2
Ide yang sangat buruk: tambahkan satu anggota ke anggota Anda addressdan Anda tidak akan pernah tahu semua tempat yang membuat addressdan sekarang tidak menginisialisasi anggota baru Anda.
mystery_doctor


17

Pengidentifikasi bidang memang sintaks initializer C. Dalam C ++ hanya memberikan nilai dalam urutan yang benar tanpa nama bidang. Sayangnya ini berarti Anda harus memberikan semuanya (sebenarnya Anda dapat menghilangkan trailing kolom bernilai nol dan hasilnya akan sama):

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

1
Ya, Anda selalu dapat menggunakan inisialisasi struct yang disejajarkan.
texasbruce

3
Ya, saat ini saya hanya menggunakan metode ini (Aligned Struct Inisialisasi). Tapi saya merasa keterbacaannya tidak bagus. Karena Struktur saya besar, penginisialisasi memiliki banyak data dan sulit bagi saya untuk melacak nilai mana yang diberikan kepada anggota mana.
Dinesh PR

7
@ DineshP.R. Kemudian tulis konstruktor!
Tuan Lister

2
@ Tuan Lister (atau siapa saja) Mungkin saya terjebak dalam awan kebodohan saat ini, tetapi apakah Anda mau menjelaskan bagaimana seorang konstruktor akan jauh lebih baik? Menurut saya ada sedikit perbedaan antara menyediakan sekelompok nilai yang tidak dinamai tergantung pesanan ke daftar penginisialisasi atau menyediakan sekelompok nilai yang tidak disebutkan namanya tergantung pesanan kepada konstruktor ...?
yano

1
@Yano Sejujurnya, saya tidak begitu ingat mengapa saya pikir seorang konstruktor akan menjadi jawaban untuk masalah tersebut. Jika saya ingat, saya akan kembali kepada Anda.
Tn. Lister

13

Fitur ini disebut inisialisasi yang ditunjuk . Ini merupakan tambahan untuk standar C99. Namun, fitur ini tidak disertakan dalam C ++ 11. Menurut Bahasa Pemrograman C ++, edisi ke 4, Bagian 44.3.3.2 (Fitur C Tidak Diadopsi oleh C ++):

Beberapa tambahan untuk C99 (dibandingkan dengan C89) sengaja tidak diadopsi dalam C ++:

[1] Array panjang variabel (VLA); gunakan vektor atau beberapa bentuk array dinamis

[2] Inisialisasi yang ditunjuk; gunakan konstruktor

Tata bahasa C99 memiliki inisialisasi yang ditunjuk [Lihat ISO / IEC 9899: 2011, Draf Komite N1570 - 12 April 2011]

6.7.9 Inisialisasi

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

Di sisi lain, C ++ 11 tidak memiliki inisialisasi yang ditunjuk [Lihat ISO / IEC 14882: 2011, N3690 Committee Draft - 15 Mei 2013]

8.5 Inisialisasi

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 }
    { }

Untuk mencapai efek yang sama, gunakan konstruktor atau daftar penginisialisasi:


9

Anda cukup menginisialisasi melalui ctor:

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

9
Ini hanya terjadi jika Anda mengontrol definisi struct address. Juga, tipe POD sering sengaja tidak memiliki konstruktor dan destruktor.
user4815162342

7

Anda bahkan dapat mengemas solusi Gui13 ke dalam pernyataan inisialisasi tunggal:

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);

Penafian: Saya tidak merekomendasikan gaya ini


Ini masih berbahaya karena memungkinkan Anda untuk menambah anggota addressdan kode masih akan dikompilasi dengan satu juta tempat hanya menginisialisasi lima anggota asli. Bagian terbaik dari inisialisasi struct adalah Anda dapat memiliki semua anggota constdan itu akan memaksa Anda untuk menginisialisasi mereka semua
mystery_doctor

7

Saya tahu pertanyaan ini cukup lama, tetapi saya menemukan cara lain untuk menginisialisasi, menggunakan constexpr dan currying:

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);

Metode ini juga berfungsi untuk variabel statis global dan bahkan variabel constexpr. Satu-satunya kelemahan adalah pemeliharaan yang buruk: Setiap kali anggota lain harus dibuat diinisialisasi menggunakan metode ini, semua metode inisialisasi anggota harus diubah.


1
Ini adalah pola pembangun . Metode anggota dapat mengembalikan referensi ke properti yang akan diubah alih-alih membuat struct baru setiap kali
phuclv

6

Saya mungkin kehilangan sesuatu di sini, mengapa tidak:

#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);
}

Saya mengkompilasi program di atas dengan kompiler MinGW C ++, dan kompiler Arduino AVR C ++, dan keduanya berjalan seperti yang diharapkan. Perhatikan #include <cstdio>
run_the_race

4
@ run_the_race, ini tentang apa kata standar c ++ tidak seperti apa perilaku kompiler yang diberikan. Namun, fitur ini hadir dalam c ++ 20.
Jon

ini hanya berfungsi jika struct adalah POD. Jadi itu akan berhenti mengkompilasi jika Anda menambahkan konstruktor ke dalamnya.
oromoiluig

5

Itu tidak diimplementasikan dalam C ++. (juga, char*string? Saya harap tidak).

Biasanya jika Anda memiliki begitu banyak parameter itu adalah bau kode yang cukup serius. Tetapi sebaliknya, mengapa tidak hanya menginisialisasi nilai struct dan kemudian menetapkan masing-masing anggota?


6
"(juga, char*string? Kuharap tidak)." - Ya, itu adalah contoh C.
Ed S.

tidak bisakah kita menggunakan char * di C ++? Saat ini saya menggunakannya dan sedang bekerja (mungkin saya melakukan sesuatu yang salah). Asumsi saya adalah bahwa kompiler akan membuat string konstan "Hamilton" & "Ontario" dan menetapkan alamat mereka ke anggota struct. Apakah benar menggunakan const char * sebagai gantinya?
Dinesh PR

8
Anda dapat menggunakan char*tetapi const char*jauh lebih aman dari tipe dan semua orang hanya menggunakan std::stringkarena jauh lebih dapat diandalkan.
Anak Anjing

Baik. Ketika saya membaca "sebagaimana disebutkan di bawah", saya berasumsi itu adalah contoh yang disalin dari suatu tempat.
Ed S.

2

Saya menemukan cara ini untuk variabel global, yang tidak perlu memodifikasi definisi struktur asli:

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

kemudian deklarasikan variabel tipe baru yang diwarisi dari tipe struct asli dan gunakan konstruktor untuk inisialisasi bidang:

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

Meskipun tidak cukup elegan dengan gaya C ...

Untuk variabel lokal memerlukan memset tambahan (ini, 0, sizeof (* this)) di awal konstruktor, jadi jelas tidak lebih buruk dan jawaban @ gui13 lebih tepat.

(Perhatikan bahwa 'temp_address' adalah variabel dengan tipe 'temp_address', namun tipe baru ini mewarisi dari 'address' dan dapat digunakan di setiap tempat di mana 'address' diharapkan, jadi tidak masalah.)


1

Saya menghadapi masalah yang sama hari ini, di mana saya memiliki struct yang ingin saya isi dengan data uji yang akan diteruskan sebagai argumen ke fungsi yang saya uji. Saya ingin memiliki vektor dari struct ini dan sedang mencari metode satu-liner untuk menginisialisasi setiap struct.

Saya akhirnya pergi dengan fungsi konstruktor di struct, yang saya percaya juga disarankan dalam beberapa jawaban untuk pertanyaan Anda.

Mungkin praktik yang buruk untuk memiliki argumen ke konstruktor memiliki nama yang sama dengan variabel anggota publik, yang memerlukan penggunaan thispointer. Seseorang dapat menyarankan suntingan jika ada cara yang lebih baik.

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;

Yang saya gunakan di dalam fungsi pengujian saya untuk memanggil fungsi yang sedang diuji dengan berbagai argumen seperti ini:

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

Itu mungkin, tetapi hanya jika struct yang Anda inisialisasi adalah struct POD (data lama biasa). Itu tidak dapat berisi metode, konstruktor, atau bahkan nilai default.


1

Dalam C ++ inisialisasi style-C digantikan oleh konstruktor yang dengan waktu kompilasi dapat memastikan bahwa hanya inisialisasi yang dilakukan (yaitu setelah inisialisasi anggota objek konsisten).

Ini adalah praktik yang baik, tetapi kadang-kadang pra-inisialisasi berguna, seperti pada contoh Anda. OOP memecahkan ini dengan kelas abstrak atau pola desain kreasi .

Menurut pendapat saya, menggunakan cara aman ini membunuh kesederhanaan dan kadang-kadang pertukaran keamanan mungkin terlalu mahal, karena kode sederhana tidak memerlukan desain canggih untuk tetap dapat dipertahankan.

Sebagai solusi alternatif, saya menyarankan untuk mendefinisikan macro menggunakan lambdas untuk menyederhanakan inisialisasi agar terlihat seperti gaya-C:

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

Makro ADDRESS diperluas ke

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

yang membuat dan memanggil lambda. Parameter makro juga dipisahkan oleh koma, jadi Anda harus memasukkan initializer ke dalam tanda kurung dan memanggil seperti

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

Anda juga bisa menulis penginisialisasi makro umum

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

tapi kemudian panggilan itu sedikit kurang indah

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

namun Anda dapat mendefinisikan makro ALAMAT menggunakan makro INIT umum dengan mudah

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


0

Terinspirasi oleh jawaban yang sangat rapi ini: ( https://stackoverflow.com/a/49572324/4808079 )

Anda dapat melakukan penutupan lamba:

// 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 $;
  }();
}

Atau, jika Anda ingin menjadi sangat mewah

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

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

Ada beberapa kelemahan terkait hal ini, sebagian besar berkaitan dengan anggota yang tidak diinisialisasi. Dari apa yang dikatakan komentar jawaban terkait, ia mengkompilasi secara efisien, meskipun saya belum mengujinya.

Secara keseluruhan, saya hanya berpikir itu pendekatan yang rapi.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.