Избавляемся от #ifndef NDEBUG - PullRequest
       15

Избавляемся от #ifndef NDEBUG

9 голосов
/ 16 февраля 2011

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

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

, а методы могут выглядеть следующим образом:

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

assert(check == 100);

Мало что страшнее, чемвсе эти #ifndef NDEBUG.К сожалению, ни один из известных мне компиляторов не может оптимизировать переменную check без этих #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;
};

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

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

«Нулевой класс» не имеет push_back () и т. Д. Нет, biggie.В любом случае большинство отладочных переменных являются целочисленными.

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

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

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

Ответы [ 4 ]

8 голосов
/ 16 февраля 2011

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

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

8 голосов
/ 16 февраля 2011

Как насчет:

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

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

5 голосов
/ 16 февраля 2011

Примерно так может работать:

#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 */ };)

0 голосов
/ 16 февраля 2011

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

#define DEBUG_VAR(T) static nullclass<T>

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

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

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

...