Jump to content
    

C++: #define vs. const

Здравствуйте! До недавнего времени я использовал только си. В настоящее время я стараюсь писать максимально на языке си++, правда мои старания пока ограничиваются классами и некоторыми другими возможностями этого языка. Суть моего вопроса: каким образом может сказаться использование только одних #define, вместо рекомендуемых const?

Просто для меня лично более понятно это:

/* Valve's pin */
#define    VLV_TURN_LEFT_PIN        0
#define    VLV_TURN_RIGHT_PIN        1
#define    VLV_UP_PIN                2
#define VLV_DOWN_PIN                    3
#define    VLV_FORWARD_PIN                4
#define    VLV_BACK_PIN                    5
#define    VLV_OPEN_PIN                    6
#define    VLV_CLOSE_PIN                    7

Чем примерно такое:

const int VLV_TURN_LEFT_PIN 0
и т.д.

В данном случае конечно можно использовать (как я правильно понял) enum.

Но мне нравятся дефайны. Может ли иметь это какие-либо неприятные последствия в будущем?

Заранее спасибо за ответы и сорри за ламерский вопрос...

Share this post


Link to post
Share on other sites

Чивото я совсем не понял ничего в Вашем вопросе :07: . #define - это работа с препроцессором (ну даже не знаю как назвать, автоматическим текстовым редактором исходного кода программы, что ли). Т.е. используя такую директиву, Вы просто заменяете в "отправляемом на компиляцию" тексте программы VLV_TURN_LEFT_PIN на 0. Ни подо что не отводится память, подстановка никак не влияет и на время работы исполняемой программы.

 

А const int VLV_TURN_LEFT_PIN = 0; это переменная типа int с именем VLV_TURN_LEFT_PIN и равная "0" которую нельзя изменять. Она лежит в памяти (как во Flash так и в RAM во время исполнения).

 

Хотя странно что Вы пропустили знак "равно", может это какой-нить "неклассический" компилятор. :07: Но вобщем #define и const не кружатся в вечном танце диалектической борьбы, это как "овальный" и "серый" :) да и по-моему, в С const есть очень давно.

 

А уж enum, да, это злобный агент C++. В "+ы" перечислениям ставят то, что при инициализации констант компилятор может выполнить проверку типов(1). А еще, поскольку перечисления - это типы, определенные пользователем то для них можно перегрузить операции (2)(по умолчанию как для int) и тем самым рискнуть попасть под расправу людей работающих над этим же проектом :biggrin: ...

Share this post


Link to post
Share on other sites

Чивото я совсем не понял ничего в Вашем вопросе :07: . #define - это работа с препроцессором (ну даже не знаю как назвать, автоматическим текстовым редактором исходного кода программы, что ли). Т.е. используя такую директиву, Вы просто заменяете в "отправляемом на компиляцию" тексте программы VLV_TURN_LEFT_PIN на 0. Ни подо что не отводится память, подстановка никак не влияет и на время работы исполняемой программы.

 

А const int VLV_TURN_LEFT_PIN = 0; это переменная типа int с именем VLV_TURN_LEFT_PIN и равная "0" которую нельзя изменять. Она лежит в памяти (как во Flash так и в RAM во время исполнения).

 

Хотя странно что Вы пропустили знак "равно", может это какой-нить "неклассический" компилятор. :07: Но вобщем #define и const не кружатся в вечном танце диалектической борьбы, это как "овальный" и "серый" :) да и по-моему, в С const есть очень давно.

 

А уж enum, да, это злобный агент C++. В "+ы" перечислениям ставят то, что при инициализации констант компилятор может выполнить проверку типов(1). А еще, поскольку перечисления - это типы, определенные пользователем то для них можно перегрузить операции (2)(по умолчанию как для int) и тем самым рискнуть попасть под расправу людей работающих над этим же проектом :biggrin: ...

Знак равно пропустил по невнимательности, конечно же он там должен быть... Я понимаю, что #define это работа с препроцессором, т.е. идет подстановка вместо макроса, того значения, которое определяет последний. Просто я читал в рекомендациях к этому языку, что следует использовать именно const. Только не понятно, какой в этом смысл...

Share this post


Link to post
Share on other sites

основной смысл в том, что

>>при инициализации констант компилятор может выполнить проверку типов(1).

Share this post


Link to post
Share on other sites

До недавнего времени я использовал только си. В настоящее время я стараюсь писать максимально на языке си++, правда мои старания пока ограничиваются классами и некоторыми другими возможностями этого языка.

Я где-то читал, что если не использовать виртуальные функции, то всё, что делается на Си++, можно сделать и на Си. Так что стоит ли огород городить?

В данном случае конечно можно использовать (как я правильно понял) enum.

Но мне нравятся дефайны. Может ли иметь это какие-либо неприятные последствия в будущем?

Моё личное мнение - никаких проблем с #define я тут не вижу. Как верно сказал DRUID3, enum хорош тем, что возможна проверка типов, но при этом придётся его явно преобразовывать к int там, где это нужно.

Конечно, пуристы из лагеря Си++ скажут, что #define - это тяжёлое наследие Си, и что в Си++ есть средства получше. Но только Вам решать, что использовать, а что нет.

Share this post


Link to post
Share on other sites

Здравствуйте! До недавнего времени я использовал только си. В настоящее время я стараюсь писать максимально на языке си++, правда мои старания пока ограничиваются классами и некоторыми другими возможностями этого языка. Суть моего вопроса: каким образом может сказаться использование только одних #define, вместо рекомендуемых const?

Просто для меня лично более понятно это:

/* Valve's pin */
#define    VLV_TURN_LEFT_PIN        0
...
#define    VLV_CLOSE_PIN                    7

Чем примерно такое:

const int VLV_TURN_LEFT_PIN 0
и т.д.

В данном случае конечно можно использовать (как я правильно понял) enum.

Но мне нравятся дефайны. Может ли иметь это какие-либо неприятные последствия в будущем?

Заранее спасибо за ответы и сорри за ламерский вопрос...

Дефайн - и в С, и в С++ это просто тупой макрос, тупой в том смысле, что когда он встречается, то производится просто тупая макроподстановка безо всякого анализа того, что получается в результате. Это зачастую является источником труднообнаруживаемых багов - в исходном коде одно, а компилятор видит другое.

 

Константный же объект - это объект, обладающий типом, поэтому он прямо попадает под контроль типов компилятора, что делает его использование безопасным и однозначным. С точки зрения реализации констатны в С++ нечем не уступают препроцессорным макросам (дефайнам). Макросы по большому счету к месту использовать только для нужд условной компиляции.

 

 

 

А const int VLV_TURN_LEFT_PIN = 0; это переменная типа int с именем VLV_TURN_LEFT_PIN и равная "0" которую нельзя изменять. Она лежит в памяти (как во Flash так и в RAM во время исполнения).

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

 

1. Явно указано внешнее связывание этого объекта - с помощью ключевого слова extern.

2. В коде присутствует взятие адреса этого константного объекта.

 

В обоих этих случах компилятору ничего не остается, как разместить объект памяти. В других случаех это не не обязательно, чем и пользуется оптимизатор компилятора. Итого, использование того же

// file.h
int a = 10;
...
void f(int x);

// file.cpp
...
f(a);

не приведет ни к какому размещению константы 'a' в памяти - будет точно такая же подстановка, как и в случае с макросом. Но уже с контролем типов.

 

А уж enum, да, это злобный агент C++. В "+ы" перечислениям ставят то, что при инициализации констант компилятор может выполнить проверку типов

О какой проверке типов идет речь?

 

(1). А еще, поскольку перечисления - это типы, определенные пользователем то для них можно перегрузить операции

Типами, определяемыми пользователем, в С++ являются классы и только они. Для них, действительно, можно перегрузить операции. Перечислымый тип enum не является типом, определяемым пользователем, и для него никакие операции перегрузить нельзя.

 

Единственным назначением перечислимых типов является ограничение принимаемых значений с возможностью задания им сивмолических имен. Перечислимый тип даже не вводит своего пространства имен (scope) и имена его значений видны в том пространстве имен, где он объявлен.

 

Кстати, перечислимый тип в нормальном виде присутствует только в С++ - он именно вводит перечень принимаемых значений. В С enum штука почти вообще безполезная - с объектами этого типа можно делать все, что угодно - присваивать им любые значения, производить арифметические действия. Словом, он по сути не отлчичается от обычных интегральных встроенных типов. В С++ такие финты не проходят - там область значений задана определением перечислимого типа, и пихнуть туда левое значение можно только принудительно (через ручное преобразование типов).

 

 

 

Я где-то читал, что если не использовать виртуальные функции, то всё, что делается на Си++, можно сделать и на Си. Так что стоит ли огород городить?

И виртуальные функции - точнее, функциональность, реализуемую ими, можно достаточно просто реализовать на С. И вообще, любую программу можно написать на ассемблере.

 

Моё личное мнение - никаких проблем с #define я тут не вижу.

Например, такую проблему Вы не видите?

 

#define DOUBLE(x) x+x

int a = 3*DOUBLE(4);

 

Что ожидаем получить? Ожидаем получить 3*8 = 24. А на деле что получим? А получим 3*4+4 = 16. Конечно, этот известный, описанный в учебниках эффект легко обходится с помощью применения скобок:

 

#define DOUBLE(x) (x+x)

 

Но там и кроме этого приколов хватает. По той же причине - скрытая от программиста подстановка значений безо всякого контроля со сторны компилятора. Именно по этой причине препроцессор в кодогенерации маст дай.

 

Как верно сказал DRUID3, enum хорош тем, что возможна проверка типов, но при этом придётся его явно преобразовывать к int там, где это нужно.

Какая именно провека типов имеется в виду?

 

Конечно, пуристы из лагеря Си++ скажут, что #define - это тяжёлое наследие Си, и что в Си++ есть средства получше. Но только Вам решать, что использовать, а что нет.

Дефайны - это наследине С. Насколько тяжелое, не берусь судить. Иногда весьма тяжеловатое. Дабы избежать пробем с дефайнами и макропостановкой, надо просто всякую вещь использовать к месту - макросы в условной компиляции, тут им замены пока нет, а в качестве констант использовать константные объекты, в качестве макрофункций - встраевыемые (inline) функции, к качестве генераторов кода - шаблоны.

Share this post


Link to post
Share on other sites

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

Ну не перегибайте палку :). В абсолютно подавляющем большинстве случаев и в 'C' enum нормально выполняет свою функцию. Да его можно постараться обмануть, да многое отдано на откуп компиляторам и некоторые компиляторы на, например, арифметические действия с enum и wanings не выдадут.... Но "почти вообще бесполезная" это явный перебор - настоятельно рекомендую пользоваться и не ограничиваться банальными #define.

Share this post


Link to post
Share on other sites

Практически все что было сказано выше в этой ветке верно. От себя хочу добавить пример который иллюстрирует преимущество констант перед define'ами.

 

Представьте себе что вы имеете #define MY_ID 123. Если при написании кода вы допустили ошибку связанную с MY_ID, и при компиляции вы получите ошибку компилятора, то имени MY_ID в описании этой ошибки не будет. Связано это с тем, что еще до компилятора препроцессор заменил MY_ID на 123. И поэтому в сообщении об ошибке будет фигурировать именно 123. А если define в код ввели не вы, а кто-то другой, то на отлов ошибки уйдет масса времени.

 

И прочитайте кстати правило номер 1 из книги Скота Майерса "Эффективное использование С++, 50 рекомендаций".

Share this post


Link to post
Share on other sites

Представьте себе что вы имеете #define MY_ID 123. Если при написании кода вы допустили ошибку связанную с MY_ID, и при компиляции вы получите ошибку компилятора, то имени MY_ID в описании этой ошибки не будет.

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

Вот с макроподстановками функций дело может оказаться действительно запутанее, но рецепт прост :)

пишите их внимательнее :), если конечно inline нельзя использовать.

Share this post


Link to post
Share on other sites

Ну не перегибайте палку :). В абсолютно подавляющем большинстве случаев и в 'C' enum нормально выполняет свою функцию. Да его можно постараться обмануть, да многое отдано на откуп компиляторам и некоторые компиляторы на, например, арифметические действия с enum и wanings не выдадут.... Но "почти вообще бесполезная" это явный перебор -

Вы хотите сказать, что на:

 

enum 
{
    SLON = 10,
    MAMONT= 15,
    KOT
} a;

a = 12;

 

компилятор С дает ошибку?

 

Мой опыт говорит, что не дает, и по Стандарту не должен - это не запрещенное действие. Максимум компилятор может выдать предупреждение. IAR для AVR, например, дает на этот код предупреждение:

 

"slon.cpp",15 Warning[Pe188]: enumerated type mixed with another type

 

типа, смешение типов.

 

А на

 

а++;

 

даже и предупреждения не дает (не обязан). Хотя тут арифметика - т.е. преобразование к целому, инкремент, присваивание.

 

 

На

 

a = SLON + KOT;

 

тоже дает то же предупреждение, что и в первом примере. В общем, как хочет, так и предупреждает. Но ошибки тут нет. Хотя все эти операции приводят к семантическим ошибкам - объект перечислимого типа принимает значения, которых в типе нет. Именно поэтому в С enum и безполезен именно как перечислимый тип.

 

А в С++ режиме ни один из этих номеров не проходит. О чем и шла речь. В С++ enum - это отдельный тип и неявных преобразований в него нет. А в С - это некий объект интегрального типа, а не именно тип с перечисленными значениями.

 

настоятельно рекомендую пользоваться и не ограничиваться банальными #define.

Мне, к счастью, С компиляторами пользоваться не приходится, у меня везде С++, поэтому перечислимыми типами пользуюсь с удовольствим и к месту - они свою роль (описанную в предыущем посте) успешно выполняют.

Share this post


Link to post
Share on other sites

Хочу добавить свои 5 коп. к всему вышеизложенному:

 

const и enum - типы C++ и они подчиняются правилам видимости (scope), т.е. если у вас есть const ABC на уровне файла, то вам никто не мешает завести int ABC где нибудь в классе, с define такой номер не пройдет.

 

Здесь можно возразить, что нефиг заводить ABC в классе если уже есть ABC на глобальном уровне (и это отчасти правильно), однако не всегда можно вспомнить где и какие глобальные константы были заведены (особенно исли их количество перевалит за пару тысяч, а файлов с их описаниями - за пару сотен), а с другой стороны, если мы поместим константы и класс в разные пространства имен (namespace), то тут все будет идеологически правильно по всем канонам OO, что все равно не избавит нас от большого облома с define'ами.

 

И еще один плюс к enum - их значения вместе с идентификаторами видят debugger'ы, в отличие от define'ов

Share this post


Link to post
Share on other sites

компилятор С дает ошибку?

Мой опыт говорит, что не дает, и по Стандарту не должен - это не запрещенное действие.

С опциями типа 'warnings are errors' - да :)

Мне достаточно warning, ибо warnig для меня лично потенциальная ошибка и следует эту проблему решать растолковывая компилятору, что от него требуетя. В крайнем случае индивидуальное подавление прагмой. Наличе warnings и подавление их оптом не приемлю категорически.

А на

а++;

даже и предупреждения не дает (не обязан). Хотя тут арифметика - т.е. преобразование к целому, инкремент, присваивание.

Не выдает не обязан и не может - формально отсутсвуюет поминание enum-ов :(. Практически во всех остальных случаях, коих много больше, предупреждения есть.

Мне, к счастью, С компиляторами пользоваться не приходится, у меня везде С++, поэтому перечислимыми типами пользуюсь с удовольствим и к месту - они свою роль (описанную в предыущем посте) успешно выполняют.

Я обычно даже 'С' (с легкой доработкой напильником) исходники плюсовым компилятором компилю из-за большей его привередливости и небольших приятных фич, но тем не менее настаиваю на полезности использования enum и в "C".

Share this post


Link to post
Share on other sites

С опциями типа 'warnings are errors' - да :)

Мне достаточно warning, ибо warnig для меня лично потенциальная ошибка и следует эту проблему решать растолковывая компилятору, что от него требуетя. В крайнем случае индивидуальное подавление прагмой. Наличе warnings и подавление их оптом не приемлю категорически.

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

 

Не выдает не обязан и не может - формально отсутсвуюет поминание enum-ов :(. Практически во всех остальных случаях, коих много больше, предупреждения есть.

Как это отсутствует? 'a' - это объект перечислимого типа, вот и поминание. К нему применяется операция инкремента, которая приводит значение объекта к величине, отсутствующей в типе этого объекта. Это есть не что иное, как семантическая ошибка! Объект 'a' перечислимого типа на деле перестает таковым быть! И в чем тогда смысл использования перечисления? Вот в С++ правила строго защищают эту ситуацию - перечислимый тип является таковым, любая попытка нарушить целостность типа объекта пресекается ошибкой. Это и делает enum перечислимым типом. Это и обуславливает целесообразность использования.

 

В С этого нет, поэтому enum в С штука почти бесполезная - годится только для описания именованных литералов, не более.

 

но тем не менее настаиваю на полезности использования enum и в "C".

Share this post


Link to post
Share on other sites

В С этого нет, поэтому enum в С штука почти бесполезная - годится только для описания именованных литералов, не более.

:)

Как известно, всегда существуют 100% эффективные средства для решения проблемы. Например, проблему перхоти хорошо решает гильотитна, но почему при наличии гильотины не пользоваться не столь эффективным шампунем против перхоти я не занаю :).

Share this post


Link to post
Share on other sites

:)

Как известно, всегда существуют 100% эффективные средства для решения проблемы. Например, проблему перхоти хорошо решает гильотитна, но почему при наличии гильотины не пользоваться не столь эффективным шампунем против перхоти я не занаю :).

Пример забавный :), но не в кассу - у гильотины побочный эффект нехороший. В перечислимом типе С++ никаких таких побочных эффектов нет. А в С от перечислимого типа только название. Думается, что Вы не будете в восторге от использования для мытья волос воды, даже если на ней написно "Шампунь". :biggrin:

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...