Что такое портативный способ реализации оператора no-op в C++?

Время от времени в C++ требуется оператор no-op. Например, при реализации assert() который отключен в конфигурации без отладки (также см. этот вопрос):

#ifdef _DEBUG
#define assert(x) if( !x ) { \
                     ThrowExcepion(__FILE__, __LINE__);\
                  } else {\
                     //noop here \
                  }
#else
#define assert(x) //noop here
#endif

До сих пор у меня сложилось впечатление, что правильный путь заключается в использовании (void)0; для неоперации:

(void)0;

однако я подозреваю, что это может вызвать предупреждения на некоторых компиляторах - что-то вроде C4555: expression has no effect; expected expression with side-effect Визуальное предупреждение C++, которое не выдается для этого конкретного случая, но выдается, когда нет приведения к void,

Это универсально портативный? Есть ли способ лучше?

12 ответов

Решение

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

Вряд ли, так как ((void)0) это то, что стандарт assert макрос расширяется до когда NDEBUG определено. Таким образом, любой компилятор, который выдает предупреждения для него, будет выдавать предупреждения всякий раз, когда код, содержащий утверждения, компилируется для выпуска. Я ожидаю, что пользователи будут считать это ошибкой.

Я полагаю, что компилятор мог бы избежать этой проблемы, предупредив о вашем предложении (void)0 при лечении только ((void)0) специально. Таким образом, вы могли бы быть лучше, используя ((void)0)но я в этом сомневаюсь.

В общем, бросить что-то, чтобы аннулировать, с или без дополнительных вмещающих паренов, идиоматически означает "игнорировать это". Например, в коде C, который преобразует параметры функции в void для того, чтобы подавить предупреждения для неиспользуемых переменных. Так что в этом отношении компилятор, который предупреждал, был бы довольно непопулярным, поскольку подавление одного предупреждения просто дало бы вам другое.

Обратите внимание, что в C++ стандартные заголовки могут включать друг друга. Поэтому, если вы используете какой-либо стандартный заголовок, assert мог быть определен этим. Таким образом, ваш код является непереносимым для этой учетной записи. Если вы говорите "универсально переносимый", вы обычно должны рассматривать любой макрос, определенный в любом стандартном заголовке, как зарезервированный идентификатор. Вы могли бы определить это, но использование другого имени для ваших собственных утверждений было бы более разумным. Я знаю, что это только пример, но я не понимаю, почему вы хотите определить assert "универсально переносимым" способом, поскольку во всех реализациях C++ он уже есть, и он не выполняет то, что вы определяете здесь.

Самым простым неработающим является отсутствие кода вообще:

#define noop

Тогда код пользователя будет иметь:

if (condition) noop; else do_something();

Альтернатива, которую вы упомянули, также не включена: (void)0;, но если вы собираетесь использовать это внутри макроса, вы должны оставить ; в сторону для звонящего добавить:

#define noop (void)0
if (condition) noop; else do_something();

(Если ; был частью макроса, то был бы дополнительный ; там)

Как насчет do { } while(0)? Да, он добавляет код, но я уверен, что большинство современных компиляторов способны его оптимизировать.

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

Я довольно поздно пришел на вечеринку, но мне нужно было то же самое для loop() в проекте Arduino, где вся обработка выполняется в процедурах обслуживания прерывания таймера (ISR). Обнаружил, что встроенный код ассемблера работал у меня без определения функции:

void loop(){
  __asm__("nop\n\t");             // Do nothing.
}

Я думаю, что цель здесь, и причина не определять макрос ни к чему, состоит в том, чтобы потребовать от пользователя добавить ;, Для этого везде, где заявление является законным, (void)0 (или же ((void)0)или другие варианты в этой связи).

Я нашел этот вопрос, потому что мне нужно было сделать то же самое в глобальном масштабе, где простое старое утверждение недопустимо. К счастью, C++11 дает нам альтернативу: static_assert(true, "NO OP"), Это может быть использовано где угодно, и выполняет мою задачу, требуя ; после макроса. (В моем случае макрос - это тег для инструмента генерации кода, который анализирует исходный файл, поэтому при компиляции кода как C++ это всегда будет NO-OP.)

Я рекомендую использовать:

static_cast<void> (0)   

И что насчет:

#define NOP() ({(void)0;})

или просто

#define NOP() ({;})

AFAIK, это универсально портативный.

#define MYDEFINE()

будет делать так же.

Другим вариантом может быть что-то вроде этого:

void noop(...) {}
#define MYDEFINE() noop()

Тем не менее, я бы придерживался (void)0 или используйте встроенные функции, такие как __noop

Есть много способов, и вот сравнение, которое я сделал с некоторыми из них в компиляторе MSVS2019 cpp.

      __asm {
        nop
       }

Перевести наnopоперация по разборке, которая занимает 1 машинный цикл.do {} while (0);генерирует некоторые инструкции, которые генерируют еще несколько циклов. Простой;ничего не генерирует.

Этот код не будет опущен при оптимизации

static void nop_func()  {   }
typedef void (*nop_func_t)();
static nop_func_t nop = &nop_func;

for (...)
{
    nop();
}
    inline void noop( ) {}

Самодокументирован

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