Perbedaan antara 'struct' dan 'typedef struct' dalam C ++?

Jawaban:

1202

Dalam C ++, hanya ada sedikit perbedaan. Ini peninggalan dari C, di mana itu membuat perbedaan.

Standar bahasa C ( C89 §3.1.2.3 , C99 §6.2.3 , dan C11 §6.2.3 ) mengamanatkan ruang nama yang terpisah untuk berbagai kategori pengidentifikasi, termasuk pengidentifikasi tag (untuk struct/ union/ enum) dan pengidentifikasi biasa (untuk typedefdan pengidentifikasi lainnya) .

Jika Anda baru saja mengatakan:

struct Foo { ... };
Foo x;

Anda akan mendapatkan kesalahan kompiler, karena Foohanya didefinisikan dalam tag namespace.

Anda harus mendeklarasikannya sebagai:

struct Foo x;

Kapan pun Anda ingin merujuk ke Foo, Anda harus selalu menyebutnya a struct Foo. Ini menjadi menjengkelkan dengan cepat, sehingga Anda dapat menambahkan typedef:

struct Foo { ... };
typedef struct Foo Foo;

Sekarang struct Foo(dalam tag namespace) dan hanya polos Foo(dalam namespace pengidentifikasi biasa) keduanya merujuk pada hal yang sama, dan Anda dapat dengan bebas mendeklarasikan objek jenis Footanpa structkata kunci.


Konstruk:

typedef struct Foo { ... } Foo;

hanyalah singkatan untuk deklarasi dan typedef.


Akhirnya,

typedef struct { ... } Foo;

mendeklarasikan struktur anonim dan membuat typedefuntuk itu. Jadi, dengan konstruk ini, ia tidak memiliki nama di namespace tag, hanya nama di namespace typedef. Ini berarti juga tidak dapat dideklarasikan ke depan. Jika Anda ingin membuat deklarasi maju, Anda harus memberikannya nama di tag namespace .


Dalam C ++, semua struct/ union/ enum/ classdeklarasi bertindak seperti mereka implisit typedef'ed, selama nama tersebut tidak disembunyikan oleh deklarasi lain dengan nama yang sama. Lihat jawaban Michael Burr untuk detail selengkapnya.

Adam Rosenfield
sumber
53
Sementara apa yang Anda katakan itu benar, AFAIK, pernyataan, 'typedef struct {...} Foo;' membuat alias untuk struct yang tidak disebutkan namanya.
dirkgently
25
Bagus, ada perbedaan tipis antara "typedef struct Foo {...} Foo;" dan "typedef struct {...} Foo;".
Adam Rosenfield
8
Di C, tag struct, tag union dan tag enumerasi berbagi satu namespace, daripada (struct dan union) menggunakan dua seperti yang diklaim di atas; namespace yang dirujuk untuk nama typedef memang terpisah. Itu berarti Anda tidak dapat memiliki keduanya 'gabungan x {...};' dan 'struct x {...};' dalam satu ruang lingkup.
Jonathan Leffler
10
Selain dari hal yang tidak cukup diketik, perbedaan lain antara dua bagian kode dalam pertanyaan adalah Foo dapat mendefinisikan konstruktor pada contoh pertama, tetapi tidak pada yang kedua (karena kelas anonim tidak dapat menentukan konstruktor atau destruktor) .
Steve Jessop
1
@Lazer: Ada yang perbedaan halus, tapi Adam berarti (karena ia terus mengatakan) bahwa Anda dapat menggunakan 'Jenis var' untuk mendeklarasikan variabel tanpa typedef.
Fred Nurk
226

Dalam artikel DDJ ini , Dan Saks menjelaskan satu area kecil di mana bug dapat menjalar jika Anda tidak mengetikkan struct Anda (dan kelas!):

Jika mau, Anda dapat membayangkan bahwa C ++ menghasilkan typedef untuk setiap nama tag, seperti

typedef class string string;

Sayangnya, ini tidak sepenuhnya akurat. Saya berharap sesederhana itu, tetapi tidak. C ++ tidak dapat menghasilkan typedefs seperti untuk struct, unions, atau enums tanpa memperkenalkan ketidakcocokan dengan C.

Misalnya, misalkan program C mendeklarasikan fungsi dan status yang dinamai struct:

int status(); struct status;

Sekali lagi, ini mungkin praktik yang buruk, tetapi C. Dalam program ini, status (dengan sendirinya) mengacu pada fungsi; status struct mengacu pada jenisnya.

Jika C ++ memang secara otomatis menghasilkan typedefs untuk tag, maka ketika Anda mengkompilasi program ini sebagai C ++, kompiler akan menghasilkan:

typedef struct status status;

Sayangnya, nama jenis ini akan bertentangan dengan nama fungsi, dan program tidak mau dikompilasi. Itu sebabnya C ++ tidak bisa begitu saja menghasilkan typedef untuk setiap tag.

Dalam C ++, tag bertindak seperti nama typedef, kecuali bahwa suatu program dapat mendeklarasikan objek, fungsi, atau enumerator dengan nama yang sama dan cakupan yang sama dengan tag. Dalam hal itu, objek, fungsi, atau nama enumerator menyembunyikan nama tag. Program dapat merujuk ke nama tag hanya dengan menggunakan kelas kata kunci, struct, union, atau enum (yang sesuai) di depan nama tag. Nama tipe yang terdiri dari salah satu kata kunci ini diikuti oleh tag adalah penspesifikasi tipe-terperinci. Misalnya, status struct dan bulan enum adalah penentu tipe yang diuraikan.

Dengan demikian, program C yang mengandung keduanya:

int status(); struct status;

berperilaku sama ketika dikompilasi sebagai C ++. Status nama sendiri mengacu pada fungsi. Program ini dapat merujuk ke tipe hanya dengan menggunakan status struct specifier tipe-elaborated.

Jadi bagaimana ini memungkinkan bug untuk menyusup ke dalam program? Pertimbangkan program di Listing 1 . Program ini mendefinisikan kelas foo dengan konstruktor default, dan operator konversi yang mengubah objek foo menjadi char const *. Ekspresi

p = foo();

pada dasarnya harus membuat objek foo dan menerapkan operator konversi. Pernyataan output selanjutnya

cout << p << '\n';

harus menampilkan kelas foo, tetapi tidak. Ini menampilkan fungsi foo.

Hasil mengejutkan ini terjadi karena program menyertakan header lib.h yang ditunjukkan pada Listing 2 . Header ini mendefinisikan fungsi yang juga bernama foo. Nama fungsi foo menyembunyikan nama kelas foo, jadi referensi ke foo pada utamanya merujuk ke fungsi, bukan kelas. main dapat merujuk ke kelas hanya dengan menggunakan specifier tipe-elaborated, seperti pada

p = class foo();

Cara untuk menghindari kebingungan seperti itu di seluruh program adalah dengan menambahkan typedef berikut untuk nama kelas foo:

typedef class foo foo;

segera sebelum atau setelah definisi kelas. Typedef ini menyebabkan konflik antara nama jenis foo dan nama fungsi foo (dari perpustakaan) yang akan memicu kesalahan waktu kompilasi.

Saya tahu tidak ada orang yang benar-benar menulis typedef ini sebagai hal yang biasa. Itu membutuhkan banyak disiplin. Karena insiden kesalahan seperti yang ada di Listing 1 mungkin cukup kecil, Anda banyak yang tidak pernah mengalami masalah ini. Tetapi jika kesalahan dalam perangkat lunak Anda dapat menyebabkan cedera tubuh, maka Anda harus menulis typedef tidak peduli seberapa kecil kesalahannya.

Saya tidak bisa membayangkan mengapa ada orang yang ingin menyembunyikan nama kelas dengan fungsi atau nama objek dalam lingkup yang sama dengan kelas. Aturan persembunyian di C adalah kesalahan, dan mereka seharusnya tidak diperluas ke kelas di C ++. Memang, Anda dapat memperbaiki kesalahan, tetapi membutuhkan disiplin pemrograman ekstra dan upaya yang seharusnya tidak perlu.

Michael Burr
sumber
11
Jika Anda mencoba "class foo ()" dan gagal: Di ISO C ++, "class foo ()" adalah konstruk ilegal (artikelnya ditulis '97, sebelum standardisasi, tampaknya). Anda dapat memasukkan "typedef class foo foo;" menjadi utama, maka Anda bisa mengatakan "foo ();" (karena itu, nama typedef lebih dekat secara leksikal daripada nama fungsi). Secara sintaksis, dalam T (), T harus berupa specifier tipe sederhana. specifier tipe yang diuraikan tidak diizinkan. Tetap saja ini adalah jawaban yang bagus, tentu saja.
Johannes Schaub - litb
Listing 1dan Listing 2tautan rusak. Silahkan lihat.
Prasoon Saurav
3
Jika Anda menggunakan konvensi penamaan yang berbeda untuk kelas dan fungsi, Anda juga menghindari konflik penamaan tanpa perlu menambahkan typedef tambahan.
zstewart
64

Satu lagi perbedaan penting: typedefs tidak dapat dideklarasikan ke depan. Jadi untuk typedefopsi Anda harus #includefile yang berisi typedef, artinya segala sesuatu yang #includeAnda .hmiliki juga termasuk file itu secara langsung membutuhkannya atau tidak, dan seterusnya. Ini pasti dapat memengaruhi waktu pembangunan Anda pada proyek yang lebih besar.

Tanpa itu typedef, dalam beberapa kasus Anda hanya dapat menambahkan deklarasi maju struct Foo;di bagian atas .hfile Anda , dan hanya #includedefinisi struct dalam .cppfile Anda .

Joe
sumber
Mengapa mengekspos definisi struct memengaruhi waktu build? Apakah kompiler melakukan pengecekan tambahan bahkan jika tidak perlu (diberi opsi typedef, sehingga kompiler tahu definisi) ketika melihat sesuatu seperti Foo * nextFoo; ?
Rich
3
Ini tidak benar-benar memeriksa ekstra, itu hanya lebih banyak kode yang perlu ditangani oleh kompiler. Untuk setiap file cpp yang menemukan bahwa typedef di suatu tempat dalam rantai sertakannya, kompilasi typedef tersebut. Dalam proyek yang lebih besar, file .h yang berisi typedef dapat dengan mudah dikompilasi ratusan kali, meskipun header yang dikompilasi sangat membantu. Jika Anda bisa lolos dengan menggunakan deklarasi maju, maka lebih mudah untuk membatasi dimasukkannya.
Joe
tolong @ komentator sebelumnya (selain pemilik posting). Saya hampir ketinggalan jawaban Anda. Tapi terimakasih untuk informasinya.
Rich
Ini tidak benar lagi di C11, di mana Anda bisa mengetikkan struct yang sama dengan nama yang sama beberapa kali.
michaelmeyer
32

Ada adalah perbedaan, tapi halus. Lihatlah seperti ini: struct Foomemperkenalkan tipe baru. Yang kedua membuat alias bernama Foo (dan bukan tipe baru) untuk structtipe yang tidak disebutkan namanya .

7.1.3 Penspesifikasi typedef

1 [...]

Sebuah nama yang dideklarasikan dengan penspesifikasi typedef menjadi nama typedef. Dalam lingkup deklarasi, nama typedef secara sintaksis setara dengan kata kunci dan nama tipe yang terkait dengan pengidentifikasi dengan cara yang dijelaskan dalam Klausa 8. Nama typedef dengan demikian merupakan sinonim untuk jenis lain. Nama typedef tidak memperkenalkan tipe baru seperti halnya deklarasi kelas (9.1) atau deklarasi enum.

8 Jika deklarasi typedef mendefinisikan kelas tanpa nama (atau enum), nama typedef pertama yang dideklarasikan oleh deklarasi menjadi tipe kelas (atau tipe enum) digunakan untuk menunjukkan tipe kelas (atau tipe enum) hanya untuk keperluan hubungan saja ( 3.5). [Contoh:

typedef struct { } *ps, S; // S is the class name for linkage purposes

Jadi, typedef selalu digunakan sebagai pengganti / sinonim untuk jenis lain.

secara langsung
sumber
10

Anda tidak dapat menggunakan deklarasi maju dengan struct typedef.

Struct itu sendiri adalah jenis anonim, sehingga Anda tidak memiliki nama sebenarnya untuk meneruskan menyatakan.

typedef struct{
    int one;
    int two;
}myStruct;

Deklarasi maju seperti ini tidak akan berfungsi:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types
Yochai Timmer
sumber
Saya tidak mendapatkan kesalahan kedua untuk prototipe fungsi. Mengapa dikatakan "redefinisi; tipe dasar yang berbeda"? kompiler tidak perlu tahu seperti apa definisi myStruct, kan? Tidak masalah diambil dengan sepotong kode (yang mengetik, atau yang meneruskan pernyataan), myStruct menunjukkan tipe struct, kan?
Rich
@ Rich Ini mengeluh bahwa ada konflik nama. Ada deklarasi maju yang mengatakan "mencari sebuah struct yang disebut myStruct" dan kemudian ada typedef yang mengubah nama struktur tanpa nama sebagai "myStruct".
Yochai Timmer
Maksud Anda meletakkan kedua typedef dan meneruskan deklarasi di file yang sama? Saya lakukan, dan gcc menyusunnya dengan baik. myStruct secara tepat diartikan sebagai struktur tanpa nama. Tag myStructtinggal di namespace tag dan typedef_ed myStructtinggal di namespace normal tempat pengidentifikasi lain seperti nama fungsi, nama variabel lokal tinggal. Jadi tidak boleh ada konflik .. Saya bisa menunjukkan kode saya jika Anda ragu ada kesalahan di dalamnya.
Rich
@ Rich GCC memberikan kesalahan yang sama, teks sedikit berbeda: gcc.godbolt.org/…
Yochai Timmer
Saya rasa saya mengerti bahwa ketika Anda hanya memiliki typedef, meneruskan deklarasi dengan typedefnama ed, tidak merujuk pada struktur yang tidak disebutkan namanya. Sebaliknya deklarasi maju menyatakan struktur tidak lengkap dengan tag myStruct. Juga, tanpa melihat definisi typedef, prototipe fungsi menggunakan typedefnama ed tidak sah. Jadi kita harus memasukkan seluruh typedef setiap kali kita perlu menggunakan myStructuntuk menunjukkan suatu tipe. Perbaiki saya jika saya salah paham. Terima kasih.
Rich
0

Perbedaan penting antara 'typedef struct' dan 'struct' dalam C ++ adalah inisialisasi anggota inline di 'typedef structs' tidak akan berfungsi.

// the 'x' in this struct will NOT be initialised to zero
typedef struct { int x = 0; } Foo;

// the 'x' in this struct WILL be initialised to zero
struct Foo { int x = 0; };
pengguna2796283
sumber
3
Tidak benar. Dalam kedua kasus xdiinisialisasi. Lihat tes di IDE online Coliru (saya menginisialisasi ke 42 sehingga lebih jelas daripada dengan nol bahwa tugas telah benar-benar terjadi).
Colin D Bennett
Sebenarnya, saya mengujinya di Visual Studio 2013 dan itu tidak diinisialisasi. Ini adalah masalah yang kami temui dalam kode produksi. Semua kompiler berbeda dan hanya harus memenuhi kriteria tertentu.
user2796283
-3

Tidak ada perbedaan dalam C ++, tapi saya percaya pada C akan memungkinkan Anda untuk mendeklarasikan instance dari struct Foo tanpa secara eksplisit melakukan:

struct Foo bar;
xian
sumber
3
Lihat @ dirkgently ini jawabannya --- ada adalah perbedaan, tapi itu halus.
Keith Pinson
-3

Struct adalah membuat tipe data. Typedef adalah untuk menetapkan nama panggilan untuk tipe data.

David Tu
sumber
12
Anda tidak mengerti pertanyaannya.
Musim dingin