Pertanyaan File header praktik terbaik untuk typedefs


Saya menggunakan shared_ptr dan STL secara ekstensif dalam sebuah proyek, dan ini mengarah ke over-panjang, tipe rawan kesalahan seperti shared_ptr< vector< shared_ptr<const Foo> > > (Saya seorang programmer ObjC berdasarkan preferensi, di mana nama-nama panjang adalah norma, dan masih ini terlalu banyak.) Akan jauh lebih jelas, saya percaya, untuk secara konsisten menyebutnya FooListPtr dan mendokumentasikan konvensi penamaan bahwa "Ptr" berarti shared_ptr dan "Daftar" berarti vektor dari shared_ptr.

Ini mudah untuk diketik, tetapi itu menyebabkan sakit kepala dengan header. Saya tampaknya memiliki beberapa opsi di mana untuk menentukan FooListPtr:

  • Foo.h. Itu memusatkan semua header dan menciptakan masalah membangun yang serius, jadi itu bukan starter.
  • FooFwd.h ("header depan"). Ini adalah apa Efektif C ++ menyarankan, berdasarkan iosfwd.h. Ini sangat konsisten, tetapi overhead menjaga dua kali jumlah header tampaknya paling menjengkelkan.
  • Common.h (satukan semuanya menjadi satu file). Ini membunuh usabilitas dengan melilitkan banyak jenis yang tidak terkait. Anda sekarang tidak bisa hanya mengambil satu objek dan memindahkannya ke proyek lain. Itu bukan starter.
  • Beberapa jenis keajaiban #define mewah yang typedef jika belum diketik. Saya memiliki ketidaksukaan kekal untuk preprocessor karena saya pikir itu membuat sulit bagi orang-orang baru untuk grok kode, tapi mungkin ....
  • Gunakan subclass vektor daripada typedef. Ini sepertinya berbahaya ...

Apakah ada praktik terbaik di sini? Bagaimana mereka berubah dalam kode nyata, ketika usabilitas, keterbacaan dan konsistensi adalah yang terpenting?

Saya telah menandai wiki komunitas ini jika yang lain ingin menambahkan opsi tambahan untuk diskusi.


51
2018-03-01 14:45


asal


Jawaban:


Saya memprogram pada proyek yang terdengar seperti itu menggunakan common.h metode. Ini bekerja sangat baik untuk proyek itu.

Ada sebuah file bernama ForwardsDecl.h yang ada di header yang dikompilasi sebelumnya dan cukup forward-mendeklarasikan semua kelas penting dan typedef yang diperlukan. Pada kasus ini unique_ptr digunakan sebagai gantinya shared_ptr, tetapi penggunaannya harus serupa. Terlihat seperti ini:

// Forward declarations
class ObjectA;
class ObjectB;
class ObjectC;

// List typedefs
typedef std::vector<std::unique_ptr<ObjectA>> ObjectAList;
typedef std::vector<std::unique_ptr<ObjectB>> ObjectBList;
typedef std::vector<std::unique_ptr<ObjectC>> ObjectCList;

Kode ini diterima oleh Visual C ++ 2010 meskipun kelas hanya diteruskan ke depan (definisi kelas penuh tidak diperlukan sehingga tidak perlu menyertakan file header masing-masing kelas). Saya tidak tahu apakah itu standar dan kompiler lainnya akan memerlukan definisi kelas penuh, tapi itu berguna bahwa tidak ada: kelas lain (ObjectD) dapat memiliki ObjectAList sebagai anggota, tanpa perlu menyertakan ObjectA.h - ini dapat benar-benar membantu mengurangi ketergantungan file header!

Pemeliharaan tidak terlalu menjadi masalah, karena deklarasi ke depan hanya perlu ditulis sekali, dan setiap perubahan selanjutnya hanya perlu terjadi dalam deklarasi lengkap di file header kelas (dan ini akan memicu lebih sedikit file sumber untuk dikompilasi ulang karena berkurang ketergantungan).

Akhirnya tampak ini dapat dibagi antara proyek (saya belum mencoba sendiri) karena bahkan jika sebuah proyek tidak benar-benar menyatakan ObjectA, itu tidak masalah karena itu hanya diteruskan ke depan dan jika Anda tidak menggunakannya kompilator tidak peduli. Oleh karena itu file dapat berisi nama-nama kelas di semua proyek yang digunakan, dan tidak masalah jika ada yang hilang untuk proyek tertentu. Semua yang diperlukan adalah tajuk deklarasi lengkap yang diperlukan (mis. ObjectA.h) termasuk dalam sumber (.cpp) file itu sebenarnya digunakan mereka.


13



Saya akan pergi dengan pendekatan gabungan dari header depan dan sejenisnya common.h header yang khusus untuk proyek Anda dan hanya mencakup semua header deklarasi maju dan hal-hal lain yang umum dan ringan.

Anda mengeluh tentang overhead menjaga dua kali jumlah header tetapi saya tidak berpikir ini harus terlalu banyak masalah: header depan biasanya hanya perlu mengetahui jenis yang sangat terbatas (satu?), Dan kadang-kadang bahkan tidak tipe lengkap.

Anda bahkan dapat mencoba membuat header secara otomatis menggunakan skrip (ini dilakukan misalnya di SeqAn) jika ada sangat banyak header.


6



+1 untuk mendokumentasikan konvensi typedef.

  • Foo.h  - bisakah kamu merinci masalah yang kamu miliki dengan itu?
  • FooFwd.h - Saya tidak akan menggunakannya secara umum, hanya pada "hotspot yang jelas". (Ya, "hotspot" adalah sulit untuk ditentukan). Itu tidak mengubah aturan IMO karena ketika Anda memperkenalkan header fwd, typedef yang terkait dari foo.h pindah ke sana.
  • Common.h  - keren untuk proyek-proyek kecil, tetapi tidak skala, saya setuju.
  • Semacam fancy #define... KUMOHON TIDAK!...
  • Gunakan subkelas vektor - tidak membuatnya lebih baik. Anda mungkin menggunakan penahanan.

Jadi di sini saran prelimenary (direvisi dari pertanyaan lain ..)

  1. Header tipe standar <boost/shared_ptr.hpp>, <vector> dll. dapat masuk ke file header / bersama yang dikompilasi sebelumnya untuk proyek tersebut. Ini tidak buruk. (Saya pribadi masih menyertakannya jika diperlukan, tetapi itu berhasil selain memasukkannya ke PCH.)

  2. Jika penampung merupakan detail penerapan, typedef pergi ke tempat penampung dinyatakan (mis. Anggota kelas pribadi jika penampung adalah anggota kelas privat)

  3. Jenis terkait (seperti FooListPtr) pergi ke tempat Foo dideklarasikan, jika tipe yang terkait adalah penggunaan utama dari tipe tersebut. Itu hampir selalu berlaku untuk beberapa jenis - mis. shared_ptr.

  4. Jika Foo mendapat header deklarasi depan yang terpisah, dan jenis yang terkait adalah ok dengan itu, itu bergerak ke FooFwd.h, juga.

  5. Jika jenis hanya terkait dengan antarmuka tertentu (mis. Parameter untuk metode publik), itu ada di sana.

  6. Jika jenisnya dibagikan (dan tidak memenuhi salah satu kriteria sebelumnya), ia mendapat header sendiri. Perhatikan bahwa ini juga berarti menarik semua dependensi.

Rasanya "jelas" bagi saya, tapi saya setuju itu tidak bagus sebagai standar pengkodean.


4



Saya menggunakan shared_ptr dan STL secara ekstensif dalam sebuah proyek, dan ini mengarah ke over-panjang, jenis rawan kesalahan seperti shared_ptr <vector <shared_ptr>> (Saya seorang programmer ObjC dengan preferensi, di mana nama-nama panjang adalah norma, dan masih ini terlalu banyak.) Akan jauh lebih jelas, saya percaya, untuk secara konsisten memanggil FooListPtr ini dan mendokumentasikan konvensi penamaan bahwa "Ptr" berarti shared_ptr dan "Daftar" berarti vektor dari shared_ptr.

sebagai permulaan, saya sarankan menggunakan struktur desain yang baik untuk pelingkupan (misalnya, ruang nama) serta nama deskriptif dan tidak disingkat untuk typedefs. FooListPtr sangat singkat, imo. tidak ada yang ingin menebak apa arti singkatan (atau terkejut menemukan Foo adalah const, shared, dll.), dan tidak ada yang ingin mengubah kode mereka hanya karena tabrakan ruang.

mungkin juga membantu untuk memilih awalan untuk typedef di pustaka Anda (serta kategori umum lainnya).

itu juga ide yang buruk untuk menyeret jenis dari lingkup yang dinyatakan mereka:

namespace MON {
namespace Diddy {
class Foo;
} /* << Diddy */

/*...*/
typedef Diddy::Foo Diddy_Foo;

} /* << MON */

ada pengecualian untuk ini:

  • tipe pribadi yang sepenuhnya ecapsualted
  • tipe yang terkandung dalam lingkup baru

sementara kita melakukannya, using dalam ruang lingkup namespace dan alias namespace harus dihindari - memenuhi syarat cakupan jika Anda ingin meminimalkan pemeliharaan di masa mendatang.

Ini mudah untuk diketik, tetapi itu menyebabkan sakit kepala dengan header. Sepertinya saya memiliki beberapa opsi di mana menentukan FooListPtr:

Foo.h. Itu memusatkan semua header dan menciptakan masalah membangun yang serius, jadi itu bukan starter.

ini bisa menjadi pilihan untuk deklarasi yang sangat bergantung pada deklarasi lainnya. menyiratkan bahwa Anda perlu membagi paket, atau ada antarmuka yang umum dan lokal untuk subsistem.

FooFwd.h ("header depan"). Inilah yang Efektif C ++ menunjukkan, berdasarkan iosfwd.h. Ini sangat konsisten, tetapi overhead menjaga dua kali jumlah header tampaknya paling menjengkelkan.

jangan khawatir tentang pemeliharaan ini, sungguh. ini adalah praktik yang baik. compiler menggunakan deklarasi maju dan typedef dengan sedikit usaha. itu tidak mengganggu karena membantu mengurangi ketergantungan Anda, dan membantu memastikan bahwa semuanya benar dan terlihat. sebenarnya tidak ada lagi yang perlu dipertahankan karena file lain mengacu pada header 'paket jenis'.

Common.h (satukan semuanya menjadi satu file). Ini membunuh usabilitas dengan melilitkan banyak jenis yang tidak terkait. Anda sekarang tidak bisa hanya mengambil satu objek dan memindahkannya ke proyek lain. Itu bukan starter.

dependensi berbasis paket dan inklusi sangat baik (ideal, benar-benar) - tidak menutup kemungkinan ini. Anda jelas harus membuat antarmuka paket (atau pustaka) yang dirancang dan terstruktur dengan baik, dan mewakili kelas komponen terkait. Anda membuat masalah yang tidak perlu dari penggunaan kembali komponen / komponen. meminimalkan data statis pustaka, dan membiarkan fasa tautan dan strip melakukan pekerjaannya. sekali lagi, simpan paket Anda kecil dan dapat digunakan kembali dan ini tidak akan menjadi masalah (asumsi pustaka / paket Anda dirancang dengan baik).

Beberapa jenis keajaiban #define mewah yang typedef jika belum diketik. Saya memiliki ketidaksukaan kekal untuk preprocessor karena saya pikir itu membuat sulit bagi orang-orang baru untuk grok kode, tapi mungkin ....

sebenarnya, Anda dapat menyatakan typedef dalam cakupan yang sama beberapa kali (misalnya, dalam dua tajuk yang terpisah) - itu bukan kesalahan.

mendeklarasikan typedef dalam ruang lingkup yang sama dengan tipe-tipe dasar yang berbeda aku s sebuah kesalahan. jelas. Anda harus menghindari ini, dan untungnya compiler memaksakan itu.

untuk menghindari ini, buat 'terjemahan membangun' yang mencakup dunia - compiler akan menandai deklarasi dari jenis-jenis tip yang tidak cocok.

mencoba menyelinap dengan mengetik minimal dan / atau ke depan (yang cukup dekat untuk gratis di kompilasi) tidak sebanding dengan usaha. kadang-kadang Anda akan membutuhkan banyak dukungan bersyarat untuk deklarasi ke depan - sekali yang didefinisikan, itu mudah (pustaka stl adalah contoh yang baik dari ini - jika Anda juga menyatakan ke depan template<typename,typename>class vector;).

sebaiknya Anda memiliki semua deklarasi yang terlihat untuk menangkap kesalahan apa pun dengan segera, dan Anda dapat menghindari preprocessor dalam kasus ini sebagai bonus.

Gunakan subclass vektor daripada typedef. Ini sepertinya berbahaya ...

sebuah subkelas dari std::vector sering ditandai sebagai "kesalahan pemula". wadah ini tidak dimaksudkan untuk menjadi subkelas. jangan menggunakan praktik buruk hanya untuk mengurangi waktu kompilasi / dependensi Anda. jika dependensi benar-benar signifikan, Anda mungkin harus menggunakan PIMPL, lagian:

// <package>.types.hpp
namespace MON {
class FooListPtr;
}

// FooListPtr.hpp
namespace MON {
class FooListPtr {
    /* ... */
private:
    shared_ptr< vector< shared_ptr<const Foo> > > d_data;
};
}

Apakah ada praktik terbaik di sini? Bagaimana mereka berubah dalam kode nyata, ketika usabilitas, keterbacaan dan konsistensi adalah yang terpenting?

akhirnya, saya telah menemukan pendekatan berbasis paket ringkas kecil yang terbaik untuk digunakan kembali, untuk mengurangi waktu kompilasi, dan meminimalkan ketergantungan.


2



Sayangnya dengan typedefs Anda harus memilih antara opsi tidak ideal untuk file header Anda. Ada kasus khusus di mana opsi satu (tepat di header kelas) berfungsi dengan baik, tetapi kedengarannya tidak akan berhasil untuk Anda. Ada juga kasus di mana opsi terakhir berfungsi dengan baik, tetapi biasanya di mana Anda menggunakan subclass untuk mengganti pola yang melibatkan kelas dengan anggota tunggal tipe std :: vector. Untuk situasi Anda, saya akan menggunakan solusi header deklarasi maju. Ada pengetikan ekstra dan overhead, tetapi tidak akan menjadi C ++ sebaliknya, bukan? Itu membuat semuanya terpisah, bersih dan cepat.


1