Каков сценарий подходящего использования #define в С++?
Я знаю основные правила, используйте inline
, enum
и const
вместо #define
, это не то, что я после этого вопроса. Я хочу знать, что считается приемлемым сценарием, в котором вы бы использовали макрос #define
и как, на С++.
Пожалуйста, не ставьте вопрос или ссылки на вопросы "define vs const" или "препроцессор против компилятора", и я уже прошел через эффективный С++ Скоттом Мейерсом, и я знаю преимущества одного над другим.
Однако после часов и часов серфинга в сети я чувствую, что #define рассматривается как некоторый аутсайдер на С++, но я уверен, что должен быть случай, когда он может быть приемлемым, даже желательно, используй это.
Чтобы получить мяч, я предполагаю, что один сценарий, который я мог бы подумать, заключается в создании макроса DEBUG
, который на его основе позволяет печатать и еще не весь код для целей отладки.
Ответы
Ответ 1
Вот несколько сценариев, в которых использование #define является хорошим решением:
Добавление диагностической информации при сохранении сигнатуры функции:
#ifdef _DEBUG
#define Log(MSG) Log((MSG), __FILE__, __LINE__);
#endif
Условная компиляция и включение охранников также являются хорошим примером (пример не указан, так как вы должны это понимать:)).
Код котла - еще один пример, но это можно легко злоупотреблять. Хорошим примером использования макросов для шаблонного кода является макрос BOOST_AUTO_TEST_CASE в Boost.UnitTest(худшим примером является набор макросов WinAPI, который сопоставляет Windows API с их макросами CHAR или WCHAR).
Другим хорошим примером является предоставление ключевых слов и настроек для компилятора:
#if (defined _WIN32) && (defined LIB_SHARED)
# ifdef LIB_EXPORTS
# define LIB_EXPORT __declspec(dllexport)
# else
# define LIB_EXPORT __declspec(dllimport)
# endif /* LIB_EXPORTS */
#else
# define LIB_EXPORT extern
#endif /* _WIN32 && LIB_SHARED */
Использование:
// forward declaration of API function:
LIB_EXPORT void MyFunction(int);
Ответ 2
Простая настройка для отладки/выпуска или для кроссплатформенного кода. Вот пример моей программы:
void Painter::render()
{
if (m_needsSorting)
{
_sort();
}
for (GameObject* o : m_objects)
{
o->render();
#ifdef _DEBUG
o->renderDebug();
#endif
}
}
и еще один для win/ios:
#ifdef _WIN32
#include "EGL/egl.h"
#include "GLES2/gl2.h"
#include <Windows.h>
#ifdef _ANALYZE
#include <vld.h>
#endif
#else // IOS
#import <Availability.h>
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#import <Foundation/Foundation.h>
#endif
другое для библиотек:
#ifdef VECTRY_INLINE
#define vinline inline
#else
#define vinline
#endif
и некоторые полезные вещи вроде этого:
#define MakeShared(T) \
class T; \
typedef shared_ptr<T> T##Ptr
Ответ 3
Один из немногих полезных случаев в С++ включает защитные устройства:
// A.h
#ifndef _A_H_
#define _A_H_
class A
{ /* ... */ };
#endif /* _A_H_ */
Ответ 4
Иногда вы хотите сгенерировать код без необходимости повторять неиспользуемый шаблон или не использовать другой язык для этого. Время от времени, шаблонов будет недостаточно, и вы будете использовать Boost.Preprocessor для генерации вашего кода.
Одним из примеров, где макросы являются "обязательными", является Boost.TTI (интроспекция типов объектов). Основной механизм каким-то образом злоупотребляет языком, чтобы создать пару мощных метафайлов, но нуждается в большом количестве шаблонов. Например, макрос BOOST_TTI_HAS_MEMBER_FUNCTION
создает функцию mate, которая проверяет, имеет ли класс заданную функцию-член. Для этого требуется создать новый класс и не может быть коротким без макроса (примеры немакровых решений для решения проблемы здесь).
Также есть несколько раз, когда вам нужно будет использовать X-macro для генерации вашего кода. Очень полезно связывать вещи во время компиляции. Я не уверен, можно ли их полностью заменить или нет, но в любом случае вы можете найти некоторые действительно интересные примеры приложений здесь.
Подводя итог, макросы могут быть мощным инструментом для генерации кода, но их нужно использовать с осторожностью.
Ответ 5
Я думаю, что когда C был введен, C не использовал константы, поэтому #defines
был единственным способом предоставления постоянных значений. Но позже #define
было мало использовано, поскольку consts занял это место (или, что более полезно, мы можем сказать, что consts были более легко использованы). Но я бы сказал, включить охранников - это еще одна область, где они используются. И они используются, так как ваш код более читабельен.
Защиты включения заголовка - это одна из областей, где вы не можете использовать consts
И пример:
#ifndef GRANDFATHER_H
#define GRANDFATHER_H
struct foo {
int member;
};
#endif /* GRANDFATHER_H */
Вы также можете проверить Почему кто-то использует #define для определения констант?
Еще одна вещь, которая добавляет, что #defines
не относится к областям, поэтому нет способа создать пространство имен с ограниченным классом, где константные переменные могут быть охвачены классами. (Я знаю, что вы знаете разницу, но думали добавить его как это важно.)
Также, чтобы показать один пример, где используется #define:
static double elapsed()
{ ... }
#define ELAPSED '[' << std::fixed << std::setprecision(2) << elapsed() << "] "
// usage:
for (vector<string>::iterator f = files.begin(); f != files.end(); f++) {
cout << ELAPSED << "reading file: " << *f << '\n';
process_file(*f);
}