Perusahaan tempat saya bekerja menginisialisasi semua struktur data mereka melalui fungsi inisialisasi seperti:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
InitializeFoo(Foo* const foo){
foo->a = x; //derived here based on other data
foo->b = y; //derived here based on other data
foo->c = z; //derived here based on other data
}
//initializing the structure
Foo foo;
InitializeFoo(&foo);
Saya mendapat sedikit dorongan untuk mencoba menginisialisasi struct saya seperti ini:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
Foo ConstructFoo(int a, int b, int c){
Foo foo;
foo.a = a; //part of parameter input (inputs derived outside of function)
foo.b = b; //part of parameter input (inputs derived outside of function)
foo.c = c; //part of parameter input (inputs derived outside of function)
return foo;
}
//initialize (or construct) the structure
Foo foo = ConstructFoo(x,y,z);
Apakah ada keuntungan satu di atas yang lain?
Yang mana yang harus saya lakukan, dan bagaimana saya membenarkannya sebagai praktik yang lebih baik?
c
coding-style
constructors
initialization
Trevor Hickey
sumber
sumber
InitializeFoo()
adalah seorang konstruktor. Satu-satunya perbedaan dari konstruktor C ++ adalah, bahwathis
pointer dilewatkan secara eksplisit daripada secara implisit. Kode yang dikompilasi dariInitializeFoo()
dan C ++ yang sesuaiFoo::Foo()
persis sama.Jawaban:
Dalam pendekatan ke-2 Anda tidak akan pernah memiliki Foo setengah-diinisialisasi. Menempatkan semua konstruksi di satu tempat tampaknya tempat yang lebih masuk akal, dan jelas.
Tapi ... cara pertama tidak begitu buruk, dan sering digunakan di banyak bidang (bahkan ada diskusi tentang cara terbaik untuk menyuntikkan ketergantungan, baik injeksi properti seperti cara pertama Anda, atau injeksi konstruktor seperti cara kedua). . Tidak ada yang salah.
Jadi jika tidak ada yang salah dan sisa perusahaan menggunakan pendekatan # 1, maka Anda harus menyesuaikan dengan basis kode yang ada dan tidak mencoba mengacaukannya dengan memperkenalkan pola baru. Ini benar-benar faktor paling penting yang dimainkan di sini, bermain baik dengan teman-teman baru Anda dan jangan mencoba menjadi kepingan salju khusus yang melakukan hal-hal berbeda.
sumber
void SetupFoo(Foo *out, int a, int b, int c)
Foo
struct "setengah diinisialisasi" ? Pendekatan pertama juga melakukan semua inisialisasi di satu tempat. (Atau apakah Anda mempertimbangkan struct yang tidak diinisialisasiFoo
untuk menjadi "setengah diinisialisasi"?)Kedua pendekatan bundel kode inisialisasi menjadi panggilan fungsi tunggal. Sejauh ini baik.
Namun, ada dua masalah dengan pendekatan kedua:
Yang kedua tidak benar-benar membangun objek yang dihasilkan, itu menginisialisasi objek lain di stack, yang kemudian disalin ke objek akhir. Inilah mengapa saya akan melihat pendekatan kedua sedikit lebih rendah. Push-back yang Anda terima kemungkinan besar karena salinan yang asing ini.
Ini bahkan lebih buruk ketika Anda mendapatkan kelas
Derived
dariFoo
(struct sebagian besar digunakan untuk orientasi objek di C): Dengan pendekatan kedua, fungsiConstructDerived()
akan memanggilConstructFoo()
, salinFoo
objek sementara yang dihasilkan ke dalam slot superclass dariDerived
objek; menyelesaikan inisialisasiDerived
objek; hanya untuk memiliki objek yang dihasilkan disalin lagi saat kembali. Tambahkan lapisan ketiga, dan semuanya menjadi sangat konyol.Dengan pendekatan kedua,
ConstructClass()
fungsi tidak memiliki akses ke alamat objek yang sedang dibangun. Ini membuat tidak mungkin untuk menghubungkan objek selama konstruksi, karena diperlukan ketika suatu objek perlu mendaftarkan dirinya dengan objek lain untuk panggilan balik.Akhirnya, tidak semua
structs
kelas penuh. Beberapastructs
secara efektif hanya membundel sekelompok variabel bersama-sama, tanpa batasan internal untuk nilai-nilai variabel ini.typedef struct Point { int x, y; } Point;
akan menjadi contoh yang baik untuk ini. Untuk penggunaan fungsi inisialisasi ini tampaknya berlebihan. Dalam kasus ini, sintaks literal majemuk mungkin lebih nyaman (C99):atau
sumber
int x = 3;
tidak menyimpan stringx
dalam biner. Alamat dan poin warisan bagus; anggapan kegagalan elisi itu bodoh.unsigned char
akan memungkinkan optimasi yang mana Aturan Aliasing yang Ketat tidak akan cukup, sementara secara bersamaan membuat harapan programmer lebih jelas.Bergantung pada isi struktur dan kompiler tertentu yang digunakan, pendekatan mana pun bisa lebih cepat. Pola tipikal adalah bahwa struktur yang memenuhi kriteria tertentu dapat dikembalikan dalam register; untuk fungsi yang mengembalikan tipe struktur lain, penelepon diharuskan mengalokasikan ruang untuk struktur sementara di suatu tempat (biasanya di stack) dan meneruskan alamatnya sebagai parameter "tersembunyi"; dalam kasus di mana fungsi kembali disimpan secara langsung ke variabel lokal yang alamatnya tidak dipegang oleh kode luar, beberapa kompiler mungkin dapat melewati alamat variabel itu secara langsung.
Jika tipe struktur memenuhi persyaratan implementasi tertentu untuk dikembalikan dalam register (mis. Tidak lebih dari satu kata mesin, atau mengisi tepat dua kata mesin) memiliki fungsi mengembalikan struktur mungkin lebih cepat daripada melewati alamat struktur, terutama karena mengekspos alamat variabel ke kode luar yang mungkin menyimpan salinannya bisa menghalangi beberapa optimasi yang bermanfaat. Jika tipe tidak memenuhi persyaratan seperti itu, kode yang dihasilkan untuk fungsi yang mengembalikan struct akan sama dengan yang untuk fungsi yang menerima pointer tujuan; kode panggilan kemungkinan akan lebih cepat untuk formulir yang mengambil pointer, tetapi formulir itu kehilangan beberapa peluang optimasi.
Sayang sekali C tidak menyediakan sarana untuk mengatakan bahwa suatu fungsi dilarang menyimpan salinan dari pass-in pointer (semantik mirip dengan referensi C ++) karena melewati pointer terbatas seperti itu akan mendapatkan keuntungan kinerja langsung dari melewatkan pointer ke objek yang sudah ada sebelumnya, tetapi pada saat yang sama menghindari biaya semantik yang memerlukan kompiler untuk mempertimbangkan alamat variabel "terbuka".
sumber
Salah satu argumen yang mendukung gaya "output-parameter" adalah bahwa ia memungkinkan fungsi untuk mengembalikan kode kesalahan.
Mempertimbangkan beberapa set struct terkait, jika inisialisasi dapat gagal untuk salah satu dari mereka, mungkin ada baiknya jika mereka semua menggunakan style out-parameter untuk konsistensi.
sumber
errno
.Saya berasumsi bahwa fokus Anda adalah pada inisialisasi melalui parameter output vs inisialisasi melalui kembali, bukan perbedaan dalam bagaimana argumen konstruksi disediakan.
Perhatikan bahwa pendekatan pertama bisa
Foo
menjadi buram (meskipun tidak dengan cara Anda menggunakannya saat ini), dan itu biasanya diinginkan untuk pemeliharaan jangka panjang. Anda dapat mempertimbangkan, misalnya, fungsi yang mengalokasikan struktur buramFoo
tanpa menginisialisasi. Atau mungkin Anda perlu menginisialisasi ulangFoo
struct yang sebelumnya diinisialisasi dengan nilai yang berbeda.sumber