J'ai essayé de trouver un moyen de déclarer des typedefs fortement typés, d'attraper une certaine classe de bogues au stade de la compilation. Il arrive souvent que je tape un int dans plusieurs types d'identifiants, ou un vecteur de position ou de vélocité:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Cela peut rendre l'intention du code plus claire, mais après une longue nuit de codage, on peut faire des erreurs stupides comme comparer différents types d'identifiants ou ajouter une position à une vélocité.
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
Malheureusement, les suggestions que j'ai trouvées pour les types de caractères fortement typés incluent l’utilisation de boost, ce qui pour moi au moins n’est pas une possibilité (j’ai au moins le c ++ 11). Donc, après un peu de réflexion, je suis tombé sur cette idée et je voulais la faire passer à quelqu'un.
Tout d'abord, vous déclarez le type de base en tant que modèle. Le paramètre template n'est utilisé pour rien dans la définition, cependant:
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 );
};
Les fonctions d'ami doivent en fait être déclarées en aval avant la définition de la classe, ce qui nécessite une déclaration en aval de la classe de modèle.
Nous définissons ensuite tous les membres pour le type de base, en nous rappelant qu'il s'agit d'une classe de modèle.
Enfin, lorsque nous voulons l’utiliser, nous le typons comme suit:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Les types sont maintenant entièrement séparés. Les fonctions qui prennent un EntityID génèreront une erreur de compilation si vous essayez de leur fournir un ModelID, par exemple. En plus de devoir déclarer les types de base en tant que modèles, avec les problèmes que cela entraîne, il est également assez compact.
J'espérais que quelqu'un avait des commentaires ou des critiques sur cette idée?
Un problème qui m'est venu à l'esprit en écrivant ceci, dans le cas des positions et des vitesses, par exemple, est que je ne peux pas convertir entre types aussi librement qu'avant. Où, avant de multiplier un vecteur par un scalaire, je devrais faire un autre vecteur:
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;
Avec mon typedef fortement typé, je devrais dire au compilateur que multiplier une Velocity par Time donne une Position.
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
Pour résoudre ce problème, je pense que je devrais spécialiser chaque conversion de manière explicite, ce qui peut être un peu gênant. D'autre part, cette limitation peut aider à prévenir d'autres types d'erreurs (par exemple, en multipliant une vitesse par une distance, ce qui n'aurait peut-être pas de sens dans ce domaine). Je suis donc déchiré et je me demande si les gens ont des opinions sur mon problème initial ou sur mon approche pour le résoudre.
la source
Réponses:
Ce sont des paramètres de type fantôme , c’est-à-dire des paramètres de type paramétré utilisés non pas pour leur représentation, mais pour séparer différents «espaces» de types avec la même représentation.
Et en parlant d’espaces, c’est une application utile des types fantômes:
Comme vous l'avez vu, les types d'unités posent quelques problèmes. Une chose que vous pouvez faire est de décomposer les unités en un vecteur d’exposants entiers sur les composants fondamentaux:
Ici, nous utilisons des valeurs fantômes pour baliser les valeurs d’exécution avec des informations au moment de la compilation sur les exposants des unités impliquées. Cela va mieux que de créer des structures séparées pour les vitesses, les distances, etc., et pourrait suffire à couvrir votre cas d'utilisation.
la source
Dans un cas similaire, je souhaitais distinguer différentes significations de certaines valeurs entières et interdire les conversions implicites entre elles. J'ai écrit un cours générique comme ceci:
Bien sûr, si vous voulez être encore plus en sécurité, vous pouvez également choisir le
T
constructeurexplicit
. LeMeaning
est ensuite utilisé comme ceci:la source
Je ne suis pas sûr de savoir comment cela fonctionne dans le code de production (je suis un débutant en programmation C ++, comme, par exemple, le débutant en CS101), mais je l’ai concocté à l’aide du système de macros du C ++.
la source