Eu tenho tentado pensar em uma maneira de declarar typedefs fortemente tipados, para capturar uma certa classe de bugs no estágio de compilação. Geralmente, digito um int em vários tipos de IDs ou um vetor para posição ou velocidade:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Isso pode tornar a intenção do código mais clara, mas após uma longa noite de codificação, pode-se cometer erros tolos como comparar diferentes tipos de IDs ou adicionar uma posição a uma velocidade, talvez.
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
Infelizmente, as sugestões que encontrei para typedefs com tipos fortes incluem o uso de boost, o que pelo menos para mim não é uma possibilidade (eu tenho c ++ 11 pelo menos). Então, depois de pensar um pouco, me deparei com essa idéia e queria executá-la por alguém.
Primeiro, você declara o tipo de base como um modelo. O parâmetro template não é usado para nada na definição, no entanto:
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 );
};
As funções de amigo realmente precisam ser declaradas para frente antes da definição da classe, o que requer uma declaração de encaminhamento da classe de modelo.
Em seguida, definimos todos os membros para o tipo base, lembrando apenas que é uma classe de modelo.
Finalmente, quando queremos usá-lo, digitamos como:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Os tipos agora estão totalmente separados. As funções que usam um EntityID geram um erro de compilador se você tentar alimentar um ModelID, por exemplo. Além de ter que declarar os tipos de base como modelos, com os problemas que isso implica, também é bastante compacto.
Eu esperava que alguém tivesse comentários ou críticas sobre essa ideia?
Uma questão que me ocorreu ao escrever isso, no caso de posições e velocidades, por exemplo, seria que não posso converter entre tipos tão livremente quanto antes. Onde antes multiplicar um vetor por um escalar daria outro vetor, para que eu pudesse:
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;
Com o meu typedef fortemente tipado, eu teria que dizer ao compilador que a desmembragem de uma velocidade por tempo resulta em uma posição.
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
Para resolver isso, acho que teria que especializar explicitamente todas as conversões, o que pode ser um incômodo. Por outro lado, essa limitação pode ajudar a evitar outros tipos de erros (por exemplo, multiplicar uma velocidade por uma distância, talvez, o que não faria sentido nesse domínio). Por isso, estou arrasado e me pergunto se as pessoas têm alguma opinião sobre o meu problema original ou minha abordagem para resolvê-lo.
fonte
Respostas:
Esses são parâmetros de tipo fantasma , ou seja, parâmetros de um tipo parametrizado que são usados não para sua representação, mas para separar “espaços” diferentes de tipos com a mesma representação.
E por falar em espaços, é uma aplicação útil de tipos fantasmas:
Como você viu, existem algumas dificuldades com os tipos de unidades. Uma coisa que você pode fazer é decompor unidades em um vetor de expoentes inteiros nos componentes fundamentais:
Aqui, estamos usando valores fantasmas para marcar valores de tempo de execução com informações em tempo de compilação sobre os expoentes nas unidades envolvidas. Isso é melhor do que criar estruturas separadas para velocidades, distâncias e assim por diante, e pode ser suficiente para cobrir seu caso de uso.
fonte
Tive um caso semelhante em que desejava distinguir significados diferentes de alguns valores inteiros e proibir conversões implícitas entre eles. Eu escrevi uma classe genérica como esta:
Obviamente, se você quer ser ainda mais seguro, também pode criar o
T
construtorexplicit
. OMeaning
é então usado assim:fonte
Não sei ao certo como funciona o seguinte no código de produção (sou iniciante em C ++ / programação, como iniciante em CS101), mas preparei isso usando o macro sys do C ++.
fonte