Отладки только для отладки в C ++? - PullRequest
12 голосов
/ 03 апреля 2010

Я реализовал ostream для вывода отладки, который отправляет в итоге отправку отладочной информации на OutputDebugString. Типичное использование выглядит следующим образом (где debug является объектом ostream):

debug << "some error\n";

Для сборок релизов, какой наименее болезненный и наиболее эффективный способ не выводить эти операторы отладки?

Ответы [ 6 ]

9 голосов
/ 03 апреля 2010

Самый распространенный (и, безусловно, самый производительный) способ - удалить их с помощью препроцессора, используя что-то вроде этого (простейшая возможная реализация):

#ifdef RELEASE
  #define DBOUT( x )
#else
  #define DBOUT( x )  x
#endif

Затем вы можете сказать

DBOUT( debug << "some error\n" );

Редактировать: Конечно, вы можете сделать DBOUT немного более сложным:

#define DBOUT( x ) \
   debug << x  << "\n"

, который допускает более приятный синтаксис:

DBOUT( "Value is " << 42 );

Второй альтернативой является определение DBOUT как потока. Это означает, что вы должны реализовать некоторый класс нулевого потока - см. Реализация no-op std :: ostream . Тем не менее, такой поток имеет накладные расходы во время сборки релиза.

7 голосов
/ 03 апреля 2010

Как насчет этого? Вы должны проверить, что он фактически оптимизируется до нуля в релизе:

#ifdef NDEBUG
    class DebugStream {};
    template <typename T>
    DebugStream &operator<<(DebugStream &s, T) { return s; }
#else
    typedef ostream DebugStream;
#endif

Вам нужно будет передать объект потока отладки как DebugStream &, а не как ostream &, поскольку в сборках релиза он не один. Это является преимуществом, поскольку, если ваш поток отладки не является ostream, это означает, что вы не несете обычного штрафа времени выполнения для нулевого потока, который поддерживает интерфейс ostream (виртуальные функции, которые фактически вызываются, но ничего не делают).

Предупреждение: я только что это придумал, обычно я делал бы что-то похожее на ответ Нейла - имел макрос, означающий «делать это только в отладочных сборках», чтобы в источнике было явно указано, что такое отладочный код и что нет. «т. Некоторые вещи, которые я на самом деле не хочу абстрагировать.

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

debug << someFunction() << "\n";

нельзя оптимизировать ни к чему, потому что компилятор не обязательно знает, что someFunction() не имеет побочных эффектов. Конечно, если у someFunction() есть побочные эффекты, вы, возможно, захотите, чтобы он вызывался в сборках релиза, но это своеобразное сочетание логирования и функционального кода.

6 голосов
/ 20 декабря 2014

Более красивый метод:

#ifdef _DEBUG
#define DBOUT cout // or any other ostream
#else
#define DBOUT 0 && cout
#endif

DBOUT << "This is a debug build." << endl;
DBOUT << "Some result: " << doSomething() << endl;

Пока вы не делаете ничего странного, функции, вызываемые и передаваемые в DBOUT, не будут вызываться в сборках релиза. Этот макрос работает из-за приоритета оператора и логического И; поскольку && имеет более низкий приоритет, чем <<, выпуск сборки компилируется DBOUT << "a" как 0 && (cout << "a"). Логическое И не оценивает выражение справа, если выражение слева оценивается как ноль или false; поскольку левое выражение всегда оценивается в ноль, правое выражение всегда удаляется любым компилятором, который стоит использовать, за исключением случаев, когда вся оптимизация отключена (и даже в этом случае явно недоступный код может игнорироваться).


Вот пример странных вещей, которые сломают этот макрос:

DBOUT << "This is a debug build." << endl, doSomething();

Смотрите запятые. doSomething() будет вызываться всегда, независимо от того, определен ли _DEBUG. Это потому, что оператор оценивается в сборках релиза как:

(0 && (cout << "This is a debug build." << endl)), doSomething();
// evaluates further to:
false, doSomething();

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

DBOUT << "Value of b: " << (a, b) << endl;

Другой пример:

(DBOUT << "Hello, ") << "World" << endl; // Compiler error on release build

В сборках выпуска это оценивается как:

(0 && (cout << "Hello, ")) << "World" << endl;
// evaluates further to:
false << "World" << endl;

, что приводит к ошибке компилятора, поскольку bool не может быть смещен влево указателем char, если не определен пользовательский оператор. Этот синтаксис также вызывает дополнительные проблемы:

(DBOUT << "Result: ") << doSomething() << endl;
// evaluates to:
false << doSomething() << endl;

Точно так же, как когда запятая использовалась плохо, doSomething() все еще вызывается, потому что ее результат должен быть передан оператору левого сдвига. (Это может произойти, только если определен пользовательский оператор, который смещает влево bool на указатель char; в противном случае возникает ошибка компилятора.)

Не ставьте скобки DBOUT << .... Если вы хотите заключить в скобки буквенное целочисленное смещение, то заключите его в скобки, но я не знаю ни одной веской причины заключить в скобки оператор потока.

3 голосов
/ 03 апреля 2010

Как и другие говорили, самый эффективный способ - использовать препроцессор. Обычно я избегаю препроцессора, но это единственное допустимое использование, которое я нашел для этого бара, защищающего заголовки.

Обычно я хочу включить возможность трассировки любого уровня в исполняемых файлах выпуска, а также в отладочных исполняемых файлах. Исполняемые файлы отладки получают более высокий уровень трассировки по умолчанию, но уровень трассировки можно установить с помощью файла конфигурации или динамически во время выполнения.

С этой целью мои макросы выглядят как

#define TRACE_ERROR if (Debug::testLevel(Debug::Error)) DebugStream(Debug::Error)
#define TRACE_INFO  if (Debug::testLevel(Debug::Info))  DebugStream(Debug::Info)
#define TRACE_LOOP  if (Debug::testLevel(Debug::Loop))  DebugStream(Debug::Loop)
#define TRACE_FUNC  if (Debug::testLevel(Debug::Func))  DebugStream(Debug::Func)
#define TRACE_DEBUG if (Debug::testLevel(Debug::Debug)) DebugStream(Debug::Debug)

Хорошая особенность использования оператора if состоит в том, что для трассировки, которая не выводится, нет затрат, код трассировки вызывается, только если он будет напечатан.

Если вы не хотите, чтобы определенный уровень не появлялся в сборках выпуска, используйте константу, которая доступна во время компиляции в операторе if.

#ifdef NDEBUG
    const bool Debug::DebugBuild = false;
#else
    const bool Debug::DebugBuild = true;
#endif

    #define TRACE_DEBUG if (Debug::DebugBuild && Debug::testLevel(Debug::Debug)) DebugStream(Debug::Debug)

Это сохраняет синтаксис iostream, но теперь компилятор оптимизирует оператор if из кода в сборках выпуска.

2 голосов
/ 04 апреля 2010
#ifdef RELEASE
  #define DBOUT( x )
#else
  #define DBOUT( x )  x
#endif

Просто используйте это в реальных операторах ostream. Вы могли бы даже написать для него один оператор.

template<typename T> Debugstream::operator<<(T&& t) {
    DBOUT(ostream << std::forward<T>(t);) // where ostream is the internal stream object or type
}

Если ваш компилятор не может оптимизировать пустые функции в режиме выпуска, тогда пришло время получить новый компилятор.

Я, конечно, использовал ссылки на rvalue и совершенную пересылку, и нет никакой гарантии, что у вас есть такой компилятор. Но вы, безусловно, можете просто использовать const ref, если ваш компилятор совместим только с C ++ 03.

1 голос
/ 04 апреля 2010

@ iain: Недостаточно места в поле для комментариев, поэтому разместите его здесь для ясности.

Использование операторов if не является плохой идеей! Я знаю, что если у операторов в макросах могут быть некоторые подводные камни, так что я должен быть особенно осторожен при их создании и использовании. Например:

if (error) TRACE_DEBUG << "error";
else do_something_for_success();

... в конечном итоге выполнит do_something_for_success(), если произойдет ошибка и операторы трассировки уровня отладки будут отключены, поскольку оператор else связывается с внутренним оператором if. Однако большинство стилей кодирования требуют использования фигурных скобок, что решит проблему.

if (error) 
{
    TRACE_DEBUG << "error";
}
else
{
    do_something_for_success();
}

В этом фрагменте кода do_something_for_success () не выполняется ошибочно, если трассировка уровня отладки отключена.

...