Почему #define - это плохо?

Возможный дубликат:
Когда макросы C ++ полезны?
Почему #define плохо и что правильный заменитель?

Кто-то сказал мне, что #define - это плохо. Ну, честно говоря, не понимаю, почему это плохо. Если все плохо, то как еще я могу это сделать?

#include <iostream>
#define stop() cin.ignore(numeric_limits<streamsize>::max(), '\n');

person shix    schedule 26.09.2011    source источник
comment
см .: Когда макросы C ++ полезны?   -  person Tim Cooper    schedule 27.09.2011
comment
Честно говоря, это дубликат противоположного вопроса?   -  person R. Martinho Fernandes    schedule 27.09.2011
comment
По теме: stackoverflow.com/questions/4715831/, stackoverflow.com/questions/1137575/ (хотя я ищу лучшее совпадение)   -  person R. Martinho Fernandes    schedule 27.09.2011
comment
пример, когда использование макроса может быть плохим [сделайте что-нибудь отличное от того, что вы хотите]: stackoverflow.com/questions/7229865/   -  person amit    schedule 27.09.2011
comment
Попробуйте создать класс с функцией stop и попробуйте вызвать его.   -  person Brendan Long    schedule 27.09.2011


Ответы (5)


#define по своей сути не является плохим. Однако обычно есть лучшие способы сделать то, что вы хотите. Рассмотрим функцию inline:

inline void stop() {
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

(На самом деле, вам даже не нужно inline для такой функции. Просто обычная функция будет работать нормально.)

person Greg Hewgill    schedule 26.09.2011
comment
Встроенная функция? Я еще не узнал об этом, я пытался использовать функцию int и void. Спасибо. : D - person shix; 27.09.2011

Это плохо, потому что неизбирательно. Везде, где в вашем коде есть stop (), он будет заменен.

Вы можете решить эту проблему, поместив этот код в отдельный метод.

person Joe    schedule 26.09.2011

В C ++ использование #define неплохо, хотя альтернативы должны быть предпочтительнее. Есть контекст, например include guards, в котором нет другой переносимой / стандартной альтернативы.

Этого следует избегать, потому что препроцессор C работает (как следует из названия) до компилятор. Он выполняет простую текстовую замену без учета других определений. Это означает, что ввод результата в компилятор иногда не имеет смысла. Учитывать:

// in some header file.
#define FOO 5

// in some source file.
int main ()
{
    // pre-compiles to: "int 5 = 2;"
    // the compiler will vomit a weird compiler error.
    int FOO = 2;
}

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

#define min(a,b) ((a<b)?(a):(b))

А затем код вроде:

#include <Windows.h>
#include <algorithm>
int main ()
{
    // pre-compiles to: "int i = std::((1<2)?(1):(2));"
    // the compiler will vomit a weird compiler error.
    int i = std::min(1, 2); 
}

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

void stop() {
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

Для констант используйте настоящие константы C ++:

// instead of
#define FOO 5

// prefer
static const int FOO = 5;

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

person André Caron    schedule 26.09.2011

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

Например, предоставленный вами фрагмент (и другие макросы кода) может быть встроенной функцией, например (непроверенной):

static inline void stop (void) {
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

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

#define f(x) x * x * x + x

с участием:

int y = f (a + 1);  // a + 1 * a + 1 * a + 1 + a + 1 (4a+2, not a^3+a)
int z = f (a++);    // a++ * a++ * a++ + a++

Первый из них полностью удивит вас своими результатами из-за приоритета операторов, а второй даст вам неопределенное поведение. Встроенные функции не страдают этими проблемами.

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

#define ERR_OK    0
#define ERR_ARG   1
: :
#define ERR_MEM  99

и это лучше делать с помощью перечислений.

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

Но, если их достаточно оклеветать, они все еще полезны для определения простых переменных, которые можно использовать для условной компиляции. Это почти все, для чего я сейчас использую их в C ++.

person paxdiablo    schedule 26.09.2011
comment
Ооо ... Мне нравится твой пример на f(a+1). Я видел a++ пример везде, но никогда и пример, касающийся порядка операций :) - person riwalk; 27.09.2011

#define сам по себе неплох, но у него есть некоторые плохие свойства. Я перечислю несколько вещей, которые мне известны:

«Функции» не работают должным образом.

Разумным кажется следующий код:

#define getmax(a,b) (a > b ? a : b)

... но что будет, если я назову это так ?:

int a = 5;
int b = 2;
int c = getmax(++a,b);    // c equals 7.

Нет, это не опечатка. c будет равно 7. Если не верите, попробуйте. Одного этого должно быть достаточно, чтобы вас напугать.

Препроцессор по своей природе глобален

Каждый раз, когда вы используете #define для определения функции (например, stop()), он действует во ВСЕХ включенных файлах после обнаружения.

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

Отладка сложнее.

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

#define NUM_CUSTOMERS 10
#define PRICE_PER_CUSTOMER 1.10

...

double something = NUM_CUSTOMERS * PRICE_PER_CUSTOMER;

если в этой строке есть ошибка, то вы НЕ увидите удобные имена переменных в сообщении об ошибке, а увидите что-то вроде этого:

double something = 10 * 1.10;

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

person riwalk    schedule 26.09.2011