Як далеко піти з набором примітивних примітивних типів, таких як 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" використовується майже скрізь. ThetypedefІНГ часто дійсно призводить до коду , який виглядає трохи як це , хоча:

DoSomething(EscalationLevel escalationLevel) {
    ...
}

Що потім змушує мене замислитися, який маркер насправді описує параметр: тип параметра або ім'я параметра?


2
ІМХО, здається досить безглуздою вправою, але я впевнений, що деякі інші не погодиться ...
Нім

1
Ці типи мають вигляд змінних імен.
Капітан Жирафа

11
Зауважте, що це створює враження, що це безпечно для типу, але це зовсім не так - typedefs просто створює псевдоніми, але ніщо не заважає вам перейти, наприклад, Minuteдо функції, яка має аргумент, оголошений як тип Second.
Джеспер

2
@Mark: подивіться на це по-іншому. Якщо ви помилитесь, визначивши цілий тип, або нові вимоги з’являться в майбутньому, і тому ви хочете змінити його, хотіли б ви змінити один typedef або хочете шукати код для кожної функції, що маніпулює роком, і змінити свій підпис? 640k достатньо для будь-кого, і все це. Відповідним недоліком typedef є те, що люди випадково або навмисно пишуть код, який покладається на те, що Рік рівно 16 біт, то він змінюється і їх код порушується.
Стів Джессоп

1
@Steve Jessop: Я не можу вирішити, чи вважаєте ви це хорошою чи поганою ідеєю :-) Перша частина, здається, на користь, друга проти. Я здогадуюсь, це має плюси і мінуси тоді.

Відповіді:


13

Назва параметра повинна описувати, що це означає - у вашому випадку рівень ескалації. Тип - це представлення значення - додавання typedefs, як у вашому прикладі, обтяжує цю частину функції-підпису, тому я б не рекомендував її.

Typedefs корисні для шаблонів або якщо ви хочете змінити тип, який використовується для певних параметрів, наприклад, під час переходу з 32-бітової на 64-бітну платформу.


Здається, це був загальний консенсус тоді. То ти б взагалі просто дотримувався "int"? У цій програмі я повинен бути дуже ефективним, коли справа стосується передачі даних, тож це буде випадком просто перетворення на int16_t (або будь-який тип найбільшого представлення, необхідний для певного елемента) під час серіалізації?

@Mark: Так, саме так і слід робити. Використовуйте typedefs для вираження розміру використовуваних типів даних, але не диференціюйте один і той же тип, що використовується в різних контекстах.
Бьорн Поллекс

Дякую - і просто для уточнення, ви б не заважали використовувати int8_t замість int, як правило, у коді. Я вважаю, що моє головне хвилювання було чимось на кшталт "Identity", що насправді є ідентифікацією, створеною базою даних. Наразі це 32-бітний, але я ще не впевнений, чи може це врешті стати 64-бітовим. Крім того, як щодо int32_t vs int? int зазвичай такий же, як int32_t, але це може бути не завжди, я думаю, на іншій платформі? Я думаю, що я повинен просто дотримуватися "int" взагалі та "int64_t" там, де це необхідно .. дякую :-)

@Mark: Важливе, що стосується typedefs, int32_tце те, що ви повинні переконатися, що вони правильні при компілюванні на різних платформах. Якщо ви очікуєте, що діапазон Identityзміниться в якийсь момент, я вважаю, що я вважаю за краще внести зміни безпосередньо у всі коди, на які впливає. Але я не впевнений, тому що мені потрібно знати більше про ваш конкретний дизайн. Ви можете зробити це окремим питанням.
Björn Pollex

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);

У цьому випадку справа не велика, але неявна конверсія може стати джерелом помилок, які важко виправити. Використання atypedef тут не допомагає, оскільки вони є лише псевдонімом типу.

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

class Money {
  Decimal amount;
  Currency currency;
};

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

Ніщо інше не впишеться в цю функцію, якщо ми не напишемо код для того, щоб це відбулося. Випадкові перетворення неможливі. Ми також можемо писати більш складні типи за потребою без особливих клопотів.


1
Ви навіть можете написати якийсь жахливий макрос, щоб зробити все це створення класу за вас. (Давай, Скізз. Ти знаєш, що хочеш.)

1
Для деяких із них може бути корисна бібліотека безпечних типів одиниць ( tuoml.sourceforge.net/html/scalar/scalar.html ), а не написання спеціального класу для кожного.
Стів Джессоп

@Chris - безумовно, не макрос, але можливо клас шаблонів. Як зазначає Стів, ці заняття вже написані.
кевін клайн

4
@Chris: Макрос називається BOOST_STRONG_TYPEDEFнасправді;)
Матьє М.

3

Використання typedefs для примітивних типів, подібних до цього, схоже на код стилю 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 який буде загартованим тим , що є , можливо , ще багато плаває в вашому коді.

Це не срібна куля і має свої застереження, але ми вважаємо, що використовувати її набагато краще, ніж ні.


Або, як запропонував Матьє М. на посаді Скізза, можна подобатися BOOST_STRONG_TYPEDEF(float, Price), але я б не пішов так далеко в середньому проекті. А може, і я. Я мушу спати на ньому. :-)
Notinlist

1

typedefв основному дозволяє надати псевдонім для type.
Це дає вам гнучкість уникати введення довгих type namesзнову і знову і робити ваше typeлегше читабельним, де ім'я псевдоніма вказує на мету або мету type.

Це більше питання, якщо ви хочете мати більше читаних імен typedefу своєму проекті.
Зазвичай я уникаю використання typedefпримітивних типів, якщо вони незвичайно довгі для введення. Я вважаю, що назви параметрів є більш показовими.


0

Я б ніколи такого не робив. Переконайтесь, що всі вони однакового розміру - це одне, але тоді вам потрібно лише називати їх цілісними типами.


0

Використовувати подібні typedefs добре, до тих пір, коли той, хто їх використовує , не повинен нічого знати про їх основне представлення . Наприклад, якщо ви хочете передати PacketLengthоб’єкт будь-якомуprintf або scanf, вам потрібно буде знати його фактичний тип, щоб ви могли вибрати правильний специфікатор перетворення. У подібних випадках typedef просто додає рівень затуманення, не купуючи нічого взамін; ви також могли просто визначити об'єкт як int32_t.

Якщо вам потрібно застосувати семантику, характерну для кожного типу (наприклад, допустимі діапазони або значення), тоді вам краще створити абстрактний тип даних і функції для роботи з цим типом, а не просто створювати typedef.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.