Ich habe versucht, eine Möglichkeit zu finden, stark typisierte typedefs zu deklarieren, um eine bestimmte Klasse von Fehlern in der Kompilierungsphase zu erkennen. Es kommt häufig vor, dass ich ein int in mehrere Arten von ids oder einen Vektor zur Positionierung oder Geschwindigkeit eingebe:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Dies kann die Absicht des Codes klarer machen, aber nach einer langen Nacht des Codierens kann es zu dummen Fehlern kommen, wie dem Vergleichen verschiedener Arten von IDs oder dem Hinzufügen einer Position zu einer Geschwindigkeit.
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
Leider habe ich Vorschläge für stark typisierte Typedefs gefunden, die die Verwendung von Boost beinhalten, was zumindest für mich keine Möglichkeit ist (ich habe zumindest C ++ 11). Nach einigem Überlegen bin ich auf diese Idee gekommen und wollte sie von jemandem ausführen lassen.
Zunächst deklarieren Sie den Basistyp als Vorlage. Der template-Parameter wird jedoch für nichts in der Definition verwendet:
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 );
};
Friend-Funktionen müssen tatsächlich vor der Klassendefinition als Forward deklariert werden, was eine Forward-Deklaration der Template-Klasse erfordert.
Anschließend definieren wir alle Elemente für den Basistyp, wobei wir uns nur daran erinnern, dass es sich um eine Vorlagenklasse handelt.
Schließlich, wenn wir es verwenden möchten, tippen wir es als:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Die Typen sind jetzt völlig getrennt. Funktionen, die eine EntityID annehmen, lösen einen Compilerfehler aus, wenn Sie versuchen, ihnen stattdessen beispielsweise eine ModelID zuzuweisen. Abgesehen davon, dass die Basistypen als Vorlagen deklariert werden müssen, ist sie mit den damit verbundenen Problemen auch recht kompakt.
Ich hatte gehofft, jemand hätte Kommentare oder Kritik zu dieser Idee?
Ein Problem, das mir beim Schreiben in den Sinn kam, zum Beispiel bei Positionen und Geschwindigkeiten, war, dass ich nicht so frei zwischen den Typen konvertieren kann wie zuvor. Wo vor dem Multiplizieren eines Vektors mit einem Skalar ein anderer Vektor erhalten würde, könnte ich Folgendes tun:
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;
Mit meinem stark typisierten typedef müsste ich dem Compiler mitteilen, dass das Multiplizieren einer Velocity mit einer Time zu einer Position führt.
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
Um dies zu lösen, müsste ich jede Konvertierung explizit spezialisieren, was eine Art Mühe sein kann. Andererseits kann diese Einschränkung helfen, andere Arten von Fehlern zu vermeiden (z. B. das Multiplizieren einer Geschwindigkeit mit einer Entfernung, was in diesem Bereich möglicherweise keinen Sinn ergibt). Also bin ich hin und her gerissen und frage mich, ob die Leute eine Meinung zu meinem ursprünglichen Problem oder meinem Lösungsansatz haben.