Как заменить этот макрос препроцессора на #include?

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


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

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 содержит содержимое исходного макроса. Однако, поскольку я #including, я могу войти в код во время выполнения при отладке.

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

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

~TYPE_NAME()
{
  //...
}

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
2 939
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

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

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 указывают на продолжающиеся проблемы. Давайте конкретизируем.

At the moment I am using a macro to define a bunch of fields and methods on various classes, like this:

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

FIELDS_AND_METHODS is a multi-line macro that uses stringizing and token-pasting operators.

I would like to replace this with the following kind of thing

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"
};

В ПОРЯДКЕ. Чтобы сделать это конкретным, нам нужен макрос 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_ в качестве примитивного элемента управления пространством имен для макросов. Это генерирует код, который я ожидал от препроцессора C. а G ++ не светится, а создает пустой объектный файл (потому что объявленные типы не используются в моем примере кода).

Обратите внимание, что для этого не требуется никаких изменений в вызывающем коде, кроме указанного в вопросе. Я думаю, что вопрос следует улучшить, используя принцип SPOT "Single Point of Truth" (или DRY "Don't Repeat Yourself"):

#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"
};

Спасибо. Создание шаблона - это не вариант. Итак, вы подразумеваете, что есть способ достичь того же эффекта, что и операторы преобразования строк и вставки токенов в FieldsNMethods.h?

mackenir 05.12.2008 18:06

Классы являются общедоступным API, и мы не хотим загрязнять их, создавая шаблоны или имея базовый класс. Кроме того, это классы C++ / CLI, поэтому частное наследование не вариант. Спасибо за дополнительную информацию, я попробую.

mackenir 05.12.2008 18:24

«загрязнять» я имею в виду «раскрыть детали реализации таким образом, чтобы они могли запутать пользователей API».

mackenir 05.12.2008 18:24

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

Nazgob 05.12.2008 18:53

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

mackenir 05.12.2008 20:22

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

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

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

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

Что я пытаюсь сделать, так это эффективно параметризовать #include так же, как можно параметризовать макрос. Так что не происходит генерации на лету. Это все (на самом деле, раньше) во время компиляции.

mackenir 05.12.2008 18:12

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

В FieldsNMethods.h

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

char *method() { return MAKE_STR( TYPE_NAME ); }

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

#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)), вам необходимо добавить еще столько слоев, прежде чем применять операторы # или ##.

Другие вопросы по теме