Typedef internal di C ++ - gaya yang baik atau gaya yang buruk?

179

Sesuatu yang saya temukan sering saya lakukan belakangan ini adalah menyatakan typedefs relevan dengan kelas tertentu di dalam kelas itu, yaitu

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

Jenis-jenis ini kemudian digunakan di tempat lain dalam kode:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

Alasan saya menyukainya:

  • Ini mengurangi kebisingan yang diperkenalkan oleh templat kelas, std::vector<Lorem>menjadi Lorem::vector, dll.
  • Ini berfungsi sebagai pernyataan niat - dalam contoh di atas, kelas Lorem dimaksudkan untuk menjadi referensi yang dihitung melalui boost::shared_ptrdan disimpan dalam vektor.
  • Hal ini memungkinkan implementasi untuk berubah - yaitu jika Lorem perlu diubah untuk menjadi referensi intrusively dihitung (via boost::intrusive_ptr) pada tahap selanjutnya maka ini akan memiliki dampak minimal pada kode.
  • Saya pikir ini terlihat lebih cantik dan lebih mudah dibaca.

Alasan saya tidak menyukainya:

  • Kadang-kadang ada masalah dengan dependensi - jika Anda ingin menanamkan, katakanlah, Lorem::vectordalam kelas lain tetapi hanya perlu (atau ingin) untuk meneruskan mendeklarasikan Lorem (sebagai lawan memperkenalkan dependensi pada file header-nya) maka Anda akhirnya harus menggunakan tipe eksplisit (misalnya, boost::shared_ptr<Lorem>bukan Lorem::ptr), yang sedikit tidak konsisten.
  • Mungkin tidak terlalu umum, dan karenanya lebih sulit untuk dipahami?

Saya mencoba untuk bersikap objektif dengan gaya pengkodean saya, jadi akan lebih baik untuk mendapatkan beberapa pendapat lain tentangnya sehingga saya dapat membedah pemikiran saya sedikit.

Will Baker
sumber

Jawaban:

153

Saya pikir itu gaya yang sangat baik, dan saya menggunakannya sendiri. Itu selalu terbaik untuk membatasi ruang lingkup nama sebanyak mungkin, dan penggunaan kelas adalah cara terbaik untuk melakukan ini di C ++. Misalnya, pustaka C ++ Standard menggunakan typedef dalam kelas.


sumber
Itu poin yang bagus, saya heran itu terlihat 'lebih cantik' adalah alam bawah sadar saya dengan lembut menunjukkan bahwa ruang lingkup yang terbatas adalah hal yang baik . Saya bertanya-tanya, apakah fakta bahwa STL menggunakannya terutama di templat kelas membuatnya menjadi penggunaan yang agak berbeda? Apakah lebih sulit untuk dibenarkan dalam kelas 'konkret'?
Will Baker
1
Yah perpustakaan standar terdiri dari template daripada kelas, tapi saya pikir pembenarannya sama untuk keduanya.
14

Ini berfungsi sebagai pernyataan niat - dalam contoh di atas, kelas Lorem dimaksudkan untuk menjadi referensi yang dihitung melalui boost :: shared_ptr dan disimpan dalam vektor.

Inilah tepatnya yang tidak dilakukannya.

Jika saya melihat 'Foo :: Ptr' dalam kode, saya sama sekali tidak tahu apakah itu shared_ptr atau Foo * (STL memiliki :: pointer typedefs yang T *, ingat) atau apa pun. Esp. jika itu adalah pointer bersama, saya tidak memberikan typedef sama sekali, tetapi tetap menggunakan shared_ptr secara eksplisit dalam kode.

Sebenarnya, saya jarang menggunakan typedef di luar Template Metaprogramming.

STL melakukan hal semacam ini sepanjang waktu

Desain STL dengan konsep yang didefinisikan dalam hal fungsi anggota dan typedef bersarang adalah cul-de-sac historis, pustaka templat modern menggunakan kelas fungsi dan sifat-sifat gratis (lihat Boost.Graph), karena ini tidak mengecualikan tipe bawaan dari memodelkan konsep dan karena itu membuat mengadaptasi jenis-jenis yang tidak dirancang dengan konsep templat perpustakaan yang diberikan dalam pikiran lebih mudah.

Jangan menggunakan STL sebagai alasan untuk membuat kesalahan yang sama.

Marc Mutz - mmutz
sumber
Saya setuju dengan bagian pertama Anda, tetapi hasil edit terakhir Anda sedikit terlihat. Jenis bertingkat seperti itu menyederhanakan definisi kelas sifat, karena mereka memberikan default yang masuk akal. Pertimbangkan std::allocator_traits<Alloc>kelas baru ... Anda tidak perlu mengkhususkannya untuk setiap pengalokasi tunggal yang Anda tulis, karena hanya meminjam jenis langsung dari Alloc.
Dennis Zickefoose
@ Dennis: Di C ++, kenyamanan harus di sisi / pengguna / perpustakaan, bukan di sisi / penulis /: pengguna menginginkan antarmuka seragam untuk suatu sifat, dan hanya kelas sifat yang bisa memberikan itu, karena alasan yang diberikan di atas). Tetapi bahkan sebagai seorangAlloc penulis, tidaklah terlalu sulit untuk mengkhususkan std::allocator_traits<>pada tipe barunya daripada menambahkan typedef yang diperlukan. Saya juga mengedit jawabannya, karena jawaban lengkap saya tidak cocok dengan komentar.
Marc Mutz - mmutz
Tapi itu ada di sisi pengguna. Sebagai pengguna dariallocator_traits mencoba untuk membuat pengalokasi kustom, saya tidak perlu repot-repot dengan lima belas anggota kelas ciri-ciri ... semua harus saya lakukan adalah mengatakan typedef Blah value_type;dan memberikan fungsi anggota yang sesuai, dan default allocator_traitsakan mencari tahu beristirahat. Selanjutnya, lihat contoh Boost.Graph Anda. Ya, itu membuat penggunaan kelas-kelas sifat ... tetapi implementasi default graph_traits<G>hanya query Guntuk typedefs internal sendiri.
Dennis Zickefoose
1
Dan bahkan perpustakaan standar 03 memanfaatkan kelas sifat yang sesuai ... filosofi perpustakaan bukanlah untuk beroperasi pada wadah secara umum, tetapi untuk beroperasi pada iterator. Jadi ini menyediakan iterator_traitskelas sehingga algoritma umum Anda dapat dengan mudah meminta informasi yang sesuai. Yang, lagi-lagi, default untuk menanyakan iterator untuk informasinya sendiri. Panjang dan pendeknya adalah bahwa ciri dan typedef internal hampir tidak eksklusif ... mereka saling mendukung.
Dennis Zickefoose
1
@ Dennis: iterator_traitsmenjadi perlu karena T*harus menjadi model RandomAccessIterator, tetapi Anda tidak dapat memasukkan typedef yang diperlukan ke dalam T*. Setelah itu iterator_traits, typedef bersarang menjadi mubazir, dan saya berharap mereka telah dihapus di sana dan kemudian. Untuk alasan yang sama (ketidakmungkinan untuk menambahkan typedef internal), T[N]tidak memodelkan Sequencekonsep STL , dan Anda memerlukan kludges seperti std::array<T,N>. Boost.Range menunjukkan bagaimana konsep Sequence modern dapat didefinisikan yang T[N]dapat memodelkan, karena tidak memerlukan typedef bersarang, atau fungsi anggota.
Marc Mutz - mmutz
8

Typdefs jelas merupakan gaya yang baik. Dan semua "alasan saya suka" Anda baik dan benar.

Tentang masalah yang Anda miliki dengan itu. Deklarasi maju bukanlah cawan suci. Anda cukup mendesain kode Anda untuk menghindari ketergantungan multi level.

Anda dapat memindahkan typedef di luar kelas tetapi Class :: ptr jauh lebih cantik daripada ClassPtr sehingga saya tidak melakukan ini. Seperti halnya ruang nama bagi saya - hal-hal tetap terhubung dalam ruang lingkup.

Terkadang saya melakukannya

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

Dan itu bisa menjadi standar untuk semua kelas domain dan dengan beberapa spesialisasi untuk yang tertentu.

Mykola Golubyev
sumber
3

STL melakukan hal semacam ini sepanjang waktu - typedefs adalah bagian dari antarmuka untuk banyak kelas di STL.

reference
iterator
size_type
value_type
etc...

semua typedef yang merupakan bagian dari antarmuka untuk berbagai kelas template STL.

Michael Burr
sumber
Benar, dan saya menduga ini adalah tempat saya pertama kali mengambilnya. Sepertinya ini akan sedikit lebih mudah dibenarkan? Saya tidak bisa membantu tetapi melihat typedef dalam templat kelas sebagai lebih mirip dengan variabel, jika Anda berpikir di sepanjang baris 'meta-programming'.
Will Baker
3

Pilihan lain untuk ini adalah ide yang bagus. Saya mulai melakukan ini ketika menulis simulasi yang harus efisien, baik dalam waktu maupun ruang. Semua tipe nilai memiliki Ptr typedef yang dimulai sebagai penambah shared pointer. Saya kemudian melakukan beberapa profil dan mengubah beberapa dari mereka untuk meningkatkan pointer yang mengganggu tanpa harus mengubah salah satu kode di mana objek-objek ini digunakan.

Perhatikan bahwa ini hanya berfungsi ketika Anda tahu di mana kelas akan digunakan, dan bahwa semua penggunaan memiliki persyaratan yang sama. Saya tidak akan menggunakan ini dalam kode perpustakaan, misalnya, karena Anda tidak bisa tahu kapan menulis perpustakaan konteks di mana ia akan digunakan.

KeithB
sumber
3

Saat ini saya sedang mengerjakan kode, yang secara intensif menggunakan jenis typedef ini. Sejauh ini tidak masalah.

Tapi saya perhatikan bahwa ada cukup sering mengetik berulang, definisi dibagi di antara beberapa kelas, dan Anda tidak pernah benar-benar tahu jenis apa yang Anda hadapi. Tugas saya adalah merangkum ukuran beberapa struktur data kompleks yang tersembunyi di balik typedefs ini - jadi saya tidak bisa mengandalkan antarmuka yang ada. Dalam kombinasi dengan tiga hingga enam tingkat ruang nama bersarang dan kemudian menjadi membingungkan.

Jadi sebelum menggunakannya, ada beberapa hal yang perlu diperhatikan

  • Apakah ada orang lain yang perlu mengetik ini? Apakah kelas banyak digunakan oleh kelas lain?
  • Apakah saya memperpendek penggunaan atau menyembunyikan kelas? (Dalam hal persembunyian Anda juga bisa memikirkan antarmuka.)
  • Apakah orang lain bekerja dengan kode tersebut? Bagaimana mereka melakukannya? Akankah mereka berpikir itu lebih mudah atau mereka akan menjadi bingung?
Bodo
sumber
1

Saya merekomendasikan untuk memindahkan typedef tersebut di luar kelas. Dengan cara ini, Anda menghapus ketergantungan langsung pada pointer bersama dan kelas vektor dan Anda bisa memasukkannya hanya saat diperlukan. Kecuali Anda menggunakan tipe-tipe itu dalam implementasi kelas Anda, saya menganggap mereka tidak boleh menjadi typedef dalam.

Alasan Anda menyukainya masih cocok, karena dipecahkan oleh tipe aliasing melalui typedef, bukan dengan mendeklarasikannya di dalam kelas Anda.

Cătălin Pitiș
sumber
Itu akan mencemari namespace anonim dengan typedefs kan ?! Masalah dengan typedef adalah ia menyembunyikan tipe aktual, yang dapat menyebabkan konflik ketika dimasukkan dalam / oleh banyak modul, yang sulit ditemukan / diperbaiki. Ini adalah praktik yang baik untuk memuat ini di ruang nama atau di dalam kelas.
Indy9000
3
Konflik nama dan polusi namespace anonim tidak ada hubungannya dengan menjaga nama ketik di dalam kelas atau di luar. Anda dapat memiliki konflik nama dengan kelas Anda, bukan dengan typedef Anda. Jadi untuk menghindari polusi nama, gunakan ruang nama. Deklarasikan kelas Anda dan typedef terkait di namespace.
Cătălin Pitiș
Argumen lain untuk menempatkan typedef di dalam kelas adalah penggunaan fungsi templatised. Ketika, katakanlah, suatu fungsi menerima tipe kontainer yang tidak dikenal (vektor atau daftar) yang berisi tipe string yang tidak dikenal (string atau varian string-conformant Anda sendiri). satu-satunya cara untuk mengetahui jenis muatan kontainer adalah dengan mengetikkan 'value_type' yang merupakan bagian dari definisi kelas kontainer.
Marius
1

Ketika typedef hanya digunakan di dalam kelas itu sendiri (yaitu dinyatakan sebagai pribadi) saya pikir itu ide yang bagus. Namun, untuk alasan tepat yang Anda berikan, saya tidak akan menggunakannya jika typedef perlu diketahui di luar kelas. Dalam hal ini saya merekomendasikan untuk memindahkan mereka di luar kelas.

Stefan Rådström
sumber