Избавление от #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: удалена вторая идея -- не будет работать.