Обновлено: Очевидно, вы захотите сделать это с помощью шаблонов или базового класса, а не макросов. К сожалению, по разным причинам я не могу использовать ни шаблоны, ни базовый класс.
На данный момент я использую макрос для определения множества полей и методов в различных классах, например:
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 заменено его значение.
Возможно ли то, что я пытаюсь сделать? Я не могу напрямую использовать операторы преобразования строк и вставки токенов, потому что я не участвую в макроопределении.





Это требует шаблона.
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"
};
Классы являются общедоступным API, и мы не хотим загрязнять их, создавая шаблоны или имея базовый класс. Кроме того, это классы C++ / CLI, поэтому частное наследование не вариант. Спасибо за дополнительную информацию, я попробую.
«загрязнять» я имею в виду «раскрыть детали реализации таким образом, чтобы они могли запутать пользователей API».
а вы для этого используете макросы? это звучит злобно! Разве вы не можете написать такой фасад, который будет предоставлять клиенту только то, что вы хотите, и иметь внутреннюю поддержку?
Мы не хотим вводить много уровней косвенного обращения, и нам все равно понадобятся макросы для передачи во внутреннюю реализацию (или иначе придется писать много одного и того же кода много раз).
Нет, вы не можете определять определения классов или функций на лету. Они должны быть указаны либо путем непосредственного ввода, либо путем определения в препроцессоре.
Как правило, нет необходимости создавать подобные классы, и определения классов создаются перед компиляцией, будь то путем ввода всего текста или с помощью какой-либо генерации кода. Иногда существует отдельный этап генерации кода (например, в текущей Visual Studio вы можете определить этапы предварительной и последующей обработки).
Теперь, если вам нужно создать разные версии некоторых классов для разных типов данных, вы должны использовать шаблоны. Таким образом нельзя создавать штампы классов с разными именами.
Последний вопрос: зачем вы это делаете? Я никогда не был в ситуации, когда что-то подобное выглядело бы полезным в C++, и на языках, где это имеет смысл, есть средства для этого.
Что я пытаюсь сделать, так это эффективно параметризовать #include так же, как можно параметризовать макрос. Так что не происходит генерации на лету. Это все (на самом деле, раньше) во время компиляции.
Вы должны обернуть строку другим макросом (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)), вам необходимо добавить еще столько слоев, прежде чем применять операторы # или ##.
Спасибо. Создание шаблона - это не вариант. Итак, вы подразумеваете, что есть способ достичь того же эффекта, что и операторы преобразования строк и вставки токенов в FieldsNMethods.h?