Как далеко зайти с примитивными типами typedef, такими как int

14

Я видел код C ++, такой как следующий со многими typedefs.

Каковы преимущества использования многих typedefподобных s по сравнению с использованием примитивов C ++? Есть ли другой подход, который также может обеспечить эти преимущества?

В конце концов, все данные хранятся в памяти или передаются по проводам в виде битов и байтов, это действительно имеет значение?

types.h:

typedef int16_t Version;
typedef int32_t PacketLength;
typedef int32_t Identity;
typedef int32_t CabinetNumber;
typedef int64_t Time64;
typedef int64_t RFID;
typedef int64_t NetworkAddress;
typedef int64_t PathfinderAddress;
typedef int16_t PathfinderPan;
typedef int16_t PathfinderChannel;
typedef int64_t HandsetSerialNumber;
typedef int16_t PinNumber;
typedef int16_t LoggingInterval;
typedef int16_t DelayMinutes;
typedef int16_t ReminderDelayMinutes;
typedef int16_t EscalationDelayMinutes;
typedef float CalibrationOffset;
typedef float AnalogValue;
typedef int8_t PathfinderEtrx;
typedef int8_t DampingFactor;
typedef int8_t RankNumber;
typedef int8_t SlavePort;
typedef int8_t EventLevel;
typedef int8_t Percent;
typedef int8_t SensorNumber;
typedef int8_t RoleCode;
typedef int8_t Hour;
typedef int8_t Minute;
typedef int8_t Second;
typedef int8_t Day;
typedef int8_t Month;
typedef int16_t Year;
typedef int8_t EscalationLevel;

Кажется логичным попытаться убедиться, что один и тот же тип всегда используется для определенной вещи, чтобы избежать переполнения, но я часто вижу код, в котором вместо этого просто используется int. typedefИНГ часто действительно приводит к коду , который выглядит немного как это , хотя:

DoSomething(EscalationLevel escalationLevel) {
    ...
}

Что тогда заставляет меня задуматься, какой токен на самом деле описывает параметр: тип параметра или имя параметра?

отметка
источник
2
ИМХО, кажется довольно бессмысленным упражнением, но я уверен, что некоторые другие не согласятся ...
Ним
1
Эти типы выглядят как имена переменных.
Капитан Жираф
11
Обратите внимание, что это создает впечатление, что это безопасно для типов, но это совсем не так - typedefs просто создают псевдонимы, но ничто не мешает вам передать, например Minute, функцию в аргумент, объявленный как тип Second.
Джеспер
2
@Mark: посмотрите на это по-другому. Если вы допустите ошибку, решив тип целого числа, или в будущем появятся новые требования, и поэтому вы хотите изменить его, захотите ли вы изменить один тип определения или вы хотите искать в коде каждую функцию, которая манипулирует годом, и изменить свою подпись? 640к хватит всем и все такое. Соответствующим недостатком typedef является то, что люди случайно или преднамеренно пишут код, основанный на том факте, что Year равен ровно 16 битам, затем он меняется и их код нарушается.
Стив Джессоп
1
@ Стив Джессоп: Я не могу решить, считаете ли вы, что это хорошая или плохая идея :-) Первая часть кажется за, а вторая - против. Я думаю, у этого есть плюсы и минусы тогда.

Ответы:

13

Имя параметра должно описывать, что оно означает - в вашем случае уровень эскалации. Тип представляет собой то, как представлено значение - добавление typedefs, как в вашем примере, запутывает эту часть сигнатуры функции, поэтому я бы не рекомендовал ее.

Typedefs полезны для шаблонов, или если вы хотите изменить тип, используемый для определенных параметров, например, при миграции с 32-битной на 64-битную платформу.

Бьёрн Поллекс
источник
Это, кажется, общий консенсус тогда. Так ты бы просто придерживался "int"? Я должен быть очень эффективным в этом приложении, когда дело доходит до передачи данных, так будет ли это случай простого преобразования в int16_t (или любой другой тип представления, необходимый для определенного элемента) во время сериализации?
@ Марк: Да, именно так вы должны это сделать. Используйте typedefs, чтобы выразить размер используемых типов данных, но не различайте один и тот же тип, используемый в разных контекстах.
Бьорн Поллекс,
Спасибо - и просто уточнить, что вы не будете беспокоиться об использовании int8_t вместо int, как правило, в коде ... Я полагаю, что моим главным беспокойством было что-то вроде "Identity", которая на самом деле является идентичностью, сгенерированной базой данных. На данный момент он 32-битный, но я пока не уверен, может ли он стать 64-битным. А как насчет int32_t против int? int обычно совпадает с int32_t, но, возможно, это не всегда может быть на другой платформе? Я думаю, что я должен просто придерживаться "int" вообще, и "int64_t", где это необходимо .. спасибо :-)
@Mark: Важная вещь о typedefs, как int32_tто, что вы должны убедиться, что они верны при компиляции на разных платформах. Если вы ожидаете, что диапазон Identityизменится в какой-то момент, я думаю, что я бы предпочел вносить изменения непосредственно во весь затронутый код. Но я не уверен, потому что мне нужно знать больше о вашем конкретном дизайне. Возможно, вы захотите сделать это отдельным вопросом.
Бьорн Поллекс,
17

Сначала я подумал «почему бы и нет», но потом мне пришло в голову, что если вы собираетесь пойти на такую ​​длину, чтобы разделить подобные типы, то лучше используйте язык. Вместо использования псевдонимов, на самом деле определяем типы:

class AnalogueValue
{
public:
    // constructors, setters, getters, etc..
private:
    float m_value;
};

Нет разницы в производительности между:

typedef float AnalogueValue;
AnalogValue a = 3.0f;
CallSomeFunction (a);

и:

AnalogValue a (3.0f); // class version
CallSomeFunction (a);

у вас также есть преимущества добавления проверки параметров и безопасности типов. Например, рассмотрим код, который имеет дело с деньгами, используя примитивные типы:

float amount = 10.00;
CallSomeFunction(amount);

Помимо проблем округления, он также позволяет любой тип, который может быть преобразован в число с плавающей точкой:

int amount = 10;
CallSomeFunction(amount);

В этом случае это не имеет большого значения, но неявные преобразования могут стать источником ошибок, которые трудно определить. Использование здесь typedefне помогает, так как они являются просто псевдонимом типа.

Использование нового типа полностью означает, что не существует неявных преобразований, если вы не кодируете оператор приведения, что является плохой идеей, особенно потому, что оно допускает неявные преобразования. Вы также можете инкапсулировать дополнительные данные:

class Money {
  Decimal amount;
  Currency currency;
};

Money m(Decimal("10.00"), Currency.USD);
CallSomeFunction(m);

Ничто другое не поместится в эту функцию, если мы не напишем код, чтобы это произошло. Случайные преобразования невозможны. Мы также можем написать более сложные типы по мере необходимости без особых хлопот.

Skizz
источник
1
Вы могли бы даже написать какой-нибудь ужасный макрос, чтобы сделать все это создание класса для вас. (Давай, Скизз. Ты знаешь, что хочешь.)
1
Для некоторых из них может быть полезна библиотека безопасных типов типов ( tuoml.sourceforge.net/html/scalar/scalar.html ), а не написание собственного класса для каждого.
Стив Джессоп
@Chris - определенно не макрос, а, возможно, класс шаблона. Как указывает Стив, эти классы уже написаны.
Кевин Клайн
4
@Chris: макрос называется на BOOST_STRONG_TYPEDEFсамом деле;)
Матье М.
3

Использование typedef для таких примитивных типов больше похоже на код в стиле C.

В C ++ вы получите интересные ошибки, как только вы попытаетесь перегрузить функции, скажем, EventLevelи Hour. Это делает лишние имена типов довольно бесполезными.

Бо Перссон
источник
2

Мы (в нашей компании) много делаем на C ++. Это помогает понять и поддерживать код. Что хорошо при перемещении людей между командами или проведении рефакторинга. Пример:

typedef float Price;
typedef int64_t JavaTimestmap;

void f(JavaTimestamp begin, JavaTimestamp end, Price income);

Мы считаем, что хорошей практикой является создание typedef для имени измерения из типа представления. Это новое имя представляет общую роль в программном обеспечении. Имя параметра является локальной ролью . Как в User sender, User receiver. В некоторых местах это может быть избыточно, void register(User user)но я не считаю это проблемой.

Позже у кого-то может возникнуть идея, что floatне лучше всего представлять цены из-за специальных правил округления бронирования, поэтому вы загружаете или реализуете BCDFloat(двоично-десятичный) тип и изменяете typedef. Там нет поиска и замены работы с floatна BCDFloatкоторый будет закаленным тем , что есть , возможно , еще много плавает в вашем коде.

Это не серебряная пуля и имеет свои предостережения, но мы думаем, что гораздо лучше ее использовать, чем нет.

Notinlist
источник
Или, как предложил Матье М. на посту Skizz, можно пойти BOOST_STRONG_TYPEDEF(float, Price), но я бы не пошел так далеко в среднем проекте. Или, может быть, я бы. Я должен спать на нем. :-)
Notinlist
1

typedefв основном позволяет дать псевдоним для type.
Это дает вам гибкость, позволяя вам не вводить long type namesснова и снова и делать ваш текст typeболее читабельным, в котором псевдоним указывает на намерение или цель type.

Это больше вопрос выбора, если вы хотите, чтобы typedefв вашем проекте было больше читаемых имен .
Обычно я избегаю использования typedefпримитивных типов, если только они не слишком длинные для типирования. Я держу свои имена параметров более показательными.

Alok Save
источник
0

Я бы никогда так не поступил. Убедиться в том, что все они имеют одинаковый размер, - это одно, но вам нужно только ссылаться на них как на целочисленные типы.

DeadMG
источник
0

Использование typedefs, как это нормально, до тех пор, пока тот, кто их использует , не должен ничего знать об их базовом представлении . Например, если вы хотите передать PacketLengthобъект одному printfили scanf, вам нужно знать его фактический тип, чтобы вы могли выбрать правильный спецификатор преобразования. В подобных случаях typedef просто добавляет уровень запутывания, не покупая ничего взамен; с тем же успехом вы могли просто определить объект как int32_t.

Если вам нужно применить семантику, специфичную для каждого типа (например, допустимые диапазоны или значения), лучше создать абстрактный тип данных и функции для работы с этим типом, а не просто создавать определение типа.

Джон Боде
источник