Как заменить этот макрос препроцессора на #include? - PullRequest
2 голосов
/ 05 декабря 2008

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

<Ч />

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

class Example
{
  // Use FIELDS_AND_METHODS macro to define some methods and fields
  FIELDS_AND_METHODS(Example)
};

FIELDS_AND_METHODS - многострочный макрос, в котором используются операторы строкового форматирования и вставки токенов.

Я бы хотел заменить это на следующее

class Example
{
  // Include FieldsNMethods.h, with TYPE_NAME preprocessor symbol
  // defined, to achieve the same result as the macro.
  #define TYPE_NAME Example
  #include "FieldsNMethods.h"
};

Здесь я # определяю имя класса (ранее параметр макроса), а файл FieldsNMethods.h содержит содержимое исходного макроса. Однако, поскольку я #include, я могу войти в код во время выполнения, при отладке.

Однако у меня возникают проблемы с «последовательностью» и «вставкой токенов» символа препроцессора TYPE_NAME в файле FieldsNMethods.h.

Например, я хочу определить деструктор класса в FieldsNMethods.h, поэтому для этого потребуется использовать значение TYPE_NAME, как показано ниже:

~TYPE_NAME()
{
  //...
}

Но с TYPE_NAME заменяется его значением.

Возможно ли то, что я пытаюсь сделать? Я не могу напрямую использовать операторы строк и вставки токенов, потому что я не нахожусь в определении макроса.

Ответы [ 4 ]

6 голосов
/ 05 декабря 2008

Это требует шаблона.

class Example<class T>
{
    ...class definition...
};

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

Добавлена ​​

@ mackenir сказал "шаблоны не вариант". Почему шаблоны не вариант? Код имитирует шаблоны старомодным предстандартным, предстандартным способом и делает это, причиняя много боли и горя. Использование шаблонов позволит избежать этой боли - хотя будет операция преобразования.

@ mackenir спросил "есть ли способ заставить вещи работать с макросами?" Да, вы можете, но вы должны использовать шаблоны - они более надежны и удобны в обслуживании. Чтобы заставить его работать с макросами, необходимо, чтобы имена функций в коде во включенном заголовке были вызовами макросов. Вам нужно пройти уровень косвенности, чтобы заставить это работать правильно:

#define PASTE_NAME(x, y) PASTE_TOKENS(x, y)
#define PASTE_TOKENS(x, y) x ## y

#define TYPE_NAME Example
int PASTE_NAME(TYPE_NAME, _function_suffix)(void) { ... }

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


Дополнительные комментарии @mackenir указывают на сохраняющиеся проблемы. Давайте сделаем это конкретным.

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

class Example
{
    // Use FIELDS_AND_METHODS macro to define some methods and fields
    FIELDS_AND_METHODS(Example)
};

FIELDS_AND_METHODS - это многострочный макрос, в котором используются операторы преобразования в строку и вставки токена.

Я хотел бы заменить это следующим видом

class Example
{
    // Include FieldsNMethods.h, with TYPE_NAME preprocessor symbol
    // defined, to achieve the same result as the macro.
    #define TYPE_NAME Example
    #include "FieldsNMethods.h"
};

OK. Чтобы сделать это конкретным, нам нужен макрос FIELDS_AND_METHODS(type), который является многострочным и использует вставку токенов (я не собираюсь иметь дело со строкой - все же будут применяться те же основные механизмы).

#define FIELDS_AND_METHODS(type) \
    type *next; \
    type() : next(0) { } \
    type * type ## _next() { return next; }

Если повезет, это объявляет член типа «указатель на тип аргумента», конструктор для этого типа и метод (в данном случае Example_next), который возвращает этот указатель.

Итак, это может быть макрос - и нам нужно заменить его так, чтобы '#include' выполняло эквивалентную работу.

Содержимое fieldsNmethods.h становится:

#ifndef TYPE_NAME
#error TYPE_NAME not defined
#endif
#define FNM_PASTE_NAME(x, y)    FNM_PASTE_TOKENS(x, y)
#define FNM_PASTE_TOKENS(x, y)  x ## y

TYPE_NAME *next;
TYPE_NAME() : next(0) { }
TYPE_NAME * FNM_PASTE_NAME(TYPE_NAME, _next)() { return next; }

#undef FNM_PASTE_NAME
#undef FNM_PASTE_TOKENS

Обратите внимание, что заголовок не будет содержать охранников множественного включения; его смысл в том, чтобы включить его несколько раз. Он также отменяет определение своих вспомогательных макросов, чтобы разрешить множественное включение (ну, поскольку переопределения были бы идентичны, они «доброкачественные» и не вызывали бы ошибку), и я поставил перед ними префикс FNM_ в качестве примитивного элемента управления пространством имен на макросы. Это генерирует код, который я ожидаю от препроцессора Си. и G ++ не увядает, но создает пустой объектный файл (потому что объявленные типы не используются в моем примере кода).

Обратите внимание, что для этого не требуется никаких изменений в коде вызова, кроме того, который указан в вопросе. Я думаю, что вопрос должен быть улучшен с использованием SPOT-принципа «Единой точки истины» (или СУХОГО «Не повторяйся»):

#define TYPE_NAME Example
class TYPE_NAME
{
    // Include FieldsNMethods.h, with TYPE_NAME preprocessor symbol
    // defined, to achieve the same result as the macro.
    #include "FieldsNMethods.h"
};
5 голосов
/ 05 декабря 2008

Вы должны добавить дополнительный слой макросов:

#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x

#define TOKENPASTE(x, y) TOKENPASTE2(x, y)
#define TOKENPASTE2(x, y) x ## y

Причина в том, что когда у вас есть макрос, препроцессор обычно рекурсивно расширяет аргументы перед выполнением подстановки макроса. Однако, если какой-либо аргумент используется с оператором строки # или оператором вставки токена ##, он не раскрывается. Следовательно, вам необходим дополнительный слой макросов, где первый слой расширяет аргументы, а второй слой выполняет расклейку строк или вставку токенов.

Если аргументы необходимо расширить несколько раз (например, #define A B, #define B C, #define C D, STRINGIZE(A)), то вам нужно добавить еще столько слоев, прежде чем применять операторы # или ##.

3 голосов
/ 05 декабря 2008

Вы должны обернуть строку в другой макрос (2 необходимы из-за того, как работает препроцессор)

In FieldsNMethods.h

#define MAKE_STR_X( _v ) # _v
#define MAKE_STR( _v ) MAKE_STR_X( _v )

char *method() { return MAKE_STR( TYPE_NAME ); }
1 голос
/ 05 декабря 2008

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

Как правило, нет необходимости генерировать классы, подобные этому, и определения классов создаются перед компиляцией, будь то ввод всего или использование какого-либо генерирования кода. Иногда существует отдельный шаг генерации кода (например, в текущей Visual Studio вы можете определить этапы предварительной и последующей обработки).

Теперь, если вам нужно было создать разные версии некоторых классов для разных типов данных, вы должны использовать шаблоны. Таким способом нельзя создавать классы штампов с разными именами.

Последний вопрос: зачем ты это делаешь? Я никогда не был в таком положении, чтобы что-то подобное выглядело бы полезным в C ++, а в тех языках, где это имеет смысл, есть возможности сделать это.

...