Я пытался придумать способ объявления строго типизированных typedefs, чтобы поймать определенный класс ошибок на этапе компиляции. Часто бывает, что я буду вводить int для нескольких типов идентификаторов или вектора для положения или скорости:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Это может сделать смысл кода более понятным, но после долгой ночи кодирования можно сделать глупые ошибки, такие как сравнение разных видов идентификаторов или добавление позиции к скорости, возможно.
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
К сожалению, предложения, которые я нашел для строго типизированных typedefs, включают использование boost, что, по крайней мере, для меня невозможно (по крайней мере, у меня есть c ++ 11). Поэтому, немного подумав, я натолкнулся на эту идею и хотел ею воспользоваться.
Сначала вы объявляете базовый тип как шаблон. Однако параметр шаблона ни для чего не используется в определении:
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 );
};
Дружественные функции на самом деле должны быть заранее объявлены перед определением класса, что требует предварительного объявления класса шаблона.
Затем мы определяем все члены для базового типа, просто помня, что это класс шаблона.
Наконец, когда мы хотим использовать его, мы определяем его как:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Типы теперь совершенно разные. Функции, которые принимают EntityID, будут выдавать ошибку компилятора, если вы, например, попытаетесь передать им ModelID. Помимо необходимости объявлять базовые типы в качестве шаблонов, со всеми вытекающими проблемами, это также довольно компактно.
Я надеялся, что у кого-нибудь были комментарии или критика по поводу этой идеи?
Одна проблема, которая пришла в голову при написании этого, например, в случае положения и скорости, заключалась в том, что я не могу конвертировать между типами так же свободно, как раньше. Где, прежде чем умножить вектор на скаляр, получим другой вектор, так что я мог бы сделать:
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;
С моим строго типизированным typedef мне пришлось бы сказать компилятору, что умножение скорости на Time приводит к позиции.
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
Чтобы решить эту проблему, я думаю, что я должен был бы явно специализировать каждое преобразование, что может быть немного неприятно. С другой стороны, это ограничение может помочь предотвратить другие виды ошибок (скажем, умножение скорости на расстояние, возможно, что не имеет смысла в этой области). Поэтому я разрываюсь и задаюсь вопросом, есть ли у людей какие-либо мнения по поводу моей первоначальной проблемы или моего подхода к ее решению.
источник
Ответы:
Это параметры фантомного типа , то есть параметры параметризованного типа, которые используются не для их представления, а для разделения разных «пространств» типов с одинаковым представлением.
И если говорить о пробелах, это полезное применение фантомных типов:
Однако, как вы видели, с типами юнитов есть некоторые трудности. Одна вещь, которую вы можете сделать, это разложить единицы в вектор целых показателей по основным компонентам:
Здесь мы используем фантомные значения, чтобы пометить значения времени выполнения с информацией времени компиляции об экспонентах на вовлеченных единицах. Это масштабируется лучше, чем создание отдельных структур для скоростей, расстояний и т. Д., И этого может быть достаточно, чтобы охватить ваш вариант использования.
источник
У меня был похожий случай, когда я хотел различить различные значения некоторых целочисленных значений и запретить неявные преобразования между ними. Я написал общий класс, как это:
Конечно, если вы хотите быть еще более безопасным, вы также можете создать
T
конструкторexplicit
.Meaning
Затем используются следующим образом:источник
Я не уверен, как это работает в производственном коде (я новичок в C ++ / программировании, например, новичок в CS101), но я сделал это с помощью макросов C ++.
источник