Saya sudah mencoba memikirkan cara mendeklarasikan typedef yang sangat diketik, untuk menangkap kelas bug tertentu pada tahap kompilasi. Seringkali saya mengetikkan int menjadi beberapa jenis id, atau vektor untuk posisi atau kecepatan:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Ini bisa membuat maksud kode lebih jelas, tetapi setelah semalaman pengkodean, orang mungkin membuat kesalahan konyol seperti membandingkan berbagai jenis id, atau menambahkan posisi ke kecepatan mungkin.
EntityID eID;
ModelID mID;
if ( eID == mID ) // <- Compiler sees nothing wrong
{ /*bug*/ }
Position p;
Velocity v;
Position newP = p + v; // bug, meant p + v*s but compiler sees nothing wrong
Sayangnya, saran yang saya temukan untuk typedef yang sangat diketik termasuk menggunakan boost, yang setidaknya bagi saya bukan kemungkinan (saya punya setidaknya c ++ 11). Jadi setelah berpikir sebentar, saya menemukan ide ini, dan ingin menjalankannya oleh seseorang.
Pertama, Anda mendeklarasikan tipe dasar sebagai templat. Parameter template tidak digunakan untuk apa pun dalam definisi, namun:
template < typename T >
class IDType
{
unsigned int m_id;
public:
IDType( unsigned int const& i_id ): m_id {i_id} {};
friend bool operator==<T>( IDType<T> const& i_lhs, IDType<T> const& i_rhs );
};
Fungsi teman sebenarnya harus dideklarasikan ke depan sebelum definisi kelas, yang memerlukan deklarasi maju dari kelas template.
Kami kemudian mendefinisikan semua anggota untuk tipe dasar, hanya mengingat bahwa itu adalah kelas templat.
Akhirnya, ketika kita ingin menggunakannya, kita mengetikkannya sebagai:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Jenis sekarang sepenuhnya terpisah. Fungsi yang mengambil EntityID akan menimbulkan kesalahan kompiler jika Anda mencoba untuk memberi mereka ModelID, misalnya. Selain harus mendeklarasikan tipe dasar sebagai templat, dengan masalah yang menyertainya, itu juga cukup kompak.
Saya berharap ada yang punya komentar atau kritik tentang ide ini?
Salah satu masalah yang terlintas dalam pikiran ketika menulis ini, dalam hal posisi dan kecepatan misalnya, adalah bahwa saya tidak dapat mengkonversi antar tipe dengan bebas seperti sebelumnya. Di mana sebelum mengalikan vektor dengan skalar akan memberikan vektor lain, jadi saya bisa melakukan:
typedef float Time;
typedef Vector3 Position;
typedef Vector3 Velocity;
Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };
Position newP = p + v*t;
Dengan typedef saya yang sangat diketik saya harus memberi tahu kompiler bahwa multypling Velocity by a Time menghasilkan Posisi.
class TimeT;
typedef Float<TimeT> Time;
class PositionT;
typedef Vector3<PositionT> Position;
class VelocityT;
typedef Vector3<VelocityT> Velocity;
Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };
Position newP = p + v*t; // Compiler error
Untuk mengatasi ini, saya pikir saya harus mengkhususkan setiap konversi secara eksplisit, yang dapat menjadi masalah. Di sisi lain, batasan ini dapat membantu mencegah jenis kesalahan lainnya (katakanlah, mengalikan Kecepatan dengan Jarak, mungkin, yang tidak masuk akal dalam domain ini). Jadi saya bingung, dan bertanya-tanya apakah orang punya pendapat tentang masalah asli saya, atau pendekatan saya untuk menyelesaikannya.
sumber
Jawaban:
Ini adalah parameter tipe hantu , yaitu, parameter tipe parameter yang digunakan bukan untuk representasi mereka, tetapi untuk memisahkan “spasi” jenis yang berbeda dengan representasi yang sama.
Dan berbicara tentang spasi, itu aplikasi yang berguna dari jenis hantu:
Seperti yang Anda lihat, ada beberapa kesulitan dengan tipe unit. Satu hal yang dapat Anda lakukan adalah menguraikan unit menjadi vektor eksponen bilangan bulat pada komponen dasar:
Di sini kita menggunakan nilai phantom untuk menandai nilai runtime dengan informasi waktu kompilasi tentang eksponen pada unit yang terlibat. Ini berskala lebih baik daripada membuat struktur terpisah untuk kecepatan, jarak, dan sebagainya, dan mungkin cukup untuk menutupi kasus penggunaan Anda.
sumber
Saya memiliki kasus serupa di mana saya ingin membedakan makna yang berbeda dari beberapa nilai integer, dan melarang konversi implisit di antara mereka. Saya menulis kelas generik seperti ini:
Tentu saja jika Anda ingin lebih aman, Anda dapat membuat
T
konstruktornyaexplicit
juga. IniMeaning
kemudian digunakan seperti ini:sumber
Saya tidak yakin bagaimana cara berikut ini bekerja dalam kode produksi (saya seorang pemula C ++ / pemrograman, seperti, pemula CS101), tapi saya memasak ini menggunakan sistem makro C ++.
sumber