Избавление от #ifndef NDEBUG

большинство моих классов имеют переменные отладки, и это заставляет их часто выглядеть так:

class A
{
    // stuff
#ifndef NDEBUG
    int check = 0;
#endif
};

и методы могут выглядеть так:

for (/* big loop */) {
    // code
#ifndef NDEBUG
    check += x;
#endif
}

assert(check == 100);

несколько вещей, которые являются уродливее, чем все эти #ifndef NDEBUG по. К сожалению, нет компилятора я знаю, что можно оптимизировать Регистрация переменная прочь без этих #ifndefs (я не знаю, разрешено ли это вообще).

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

#ifndef NDEBUG

#define DEBUG_VAR(T) T

#else

template <typename T>
struct nullclass {
    inline void operator+=(const T&) const {}
    inline const nullclass<T>& operator+(const T&) const { return *this; }
    // more no-op operators...
};

#define DEBUG_VAR(T) nullclass<T>

#endif

поэтому в режиме отладки DEBUG_VAR (T) просто делает T. В противном случае он делает "нулевой класс" только с no-ops. И мой код будет выглядеть так:

class A {
   // stuff
   DEBUG_VAR(int) check;
};

тогда я мог бы просто использовать Регистрация как если бы это была нормальная переменная! Потрясающе! Однако, есть еще 2 проблемы, которые я не могу решить:

1. Он работает только с int, float, и т. д.

в "нулевой класс" не имеет push_back() и т. д. Ничего страшного. Наиболее переменные отладки все равно являются ints.

2. "Нулевой класс" имеет ширину 1 символ!!

каждый класс В C++ имеет ширину не менее 1 символа. Поэтому даже в режиме выпуска класс, который использует N debug vars, будет по крайней мере N символов слишком большим. Это в моих глазах просто недопустимо. Это противоречит принципу нулевых накладных расходов, к которому я стремлюсь, насколько могу.

и как исправить эту вторую проблему? можно ли даже избавиться от #ifndef NDEBUG без ущерба для производительности в режиме без отладки? Я принимаю любое хорошее решение, даже если это ваше самое темное волшебство C++ или C++0x.

4 ответов


Как насчет:

#ifndef NDEBUG
#define DEBUG_VAR(T) static nullclass<T>
#endif

теперь дополнительное хранилище не добавляется в класс, где DEBUG_VAR(T) используется как член, но объявленный идентификатор по-прежнему может использоваться как член.


вы не можете исправить 2-ю проблему, так как стандарт c++ требует, чтобы размер класса или объекта был не менее одного байта.

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


что-то вроде этого может сработать:

#ifdef NDEBUG
    #define DEBUG_VAR(type, name)
    #define DEBUG_VAR_OP(code)
#else
    #define DEBUG_VAR(type, name) type name;
    #define DEBUG_VAR_OP(code) code;
#endif

пример использования:

struct Foo
{
    DEBUG_VAR(int, count)
};

void bar(Foo* f)
{
    DEBUG_VAR_OP(f->count = 45)
}

однако обратите внимание, что в целом, чем больше различий с точки зрения макета памяти между различными конфигурациями вашей программы, тем больше жестких ошибок ("он работает в отладке, но случайно падает в выпуске") вы получите. Поэтому, если вы часто используете дополнительные отладочные данные, вы должны перепроектировать свои структуры данных. Когда есть много отладочных данных, предпочитайте оставляя указатель на отладку данных в режиме выпуска (т. е. struct Foo { ... ; struct FooDebug* debugData; /* NULL in Release */ };)


Как насчет объявления объекта-члена статическим в режиме отладки:

#define DEBUG_VAR(T) static nullclass<T>

вам нужно будет где-то определить экземпляр каждого объекта.

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

Edit: удалена вторая идея -- не будет работать.