Я реализовал ostream
для вывода отладки, который отправляет, заканчивает тем, что отправил информацию об отладке в OutputDebugString
. Типичное использование его похоже на это (где debug
объект ostream):
debug << "some error\n";
Для сборок конечных версий, что является наименее болезненным и большая часть производительного способа не произвести эти операторы отладки?
Как насчет этого? Вам нужно будет проверить, что он фактически ничего не оптимизирует в выпуске:
#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 ()
действительно имеет побочные эффекты, тогда вы можете захотеть, чтобы он вызывался в сборках релизов, но это своеобразное сочетание протоколирования и функционального кода.
#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.
Как уже было сказано, наиболее эффективный способ - использовать препроцессор. Обычно я избегаю препроцессора, но это единственное допустимое применение, которое я нашел для него, защищая заголовки.
Обычно мне нужна возможность включить любой уровень трассировки в исполняемых файлах выпуска, а также в исполняемых файлах отладки. Исполняемые файлы отладки получают более высокий уровень трассировки по умолчанию, но уровень трассировки может быть установлен файлом конфигурации или динамически во время выполнения.
В связи с этим мои макросы выглядят так:
#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 вне кода в сборках выпуска.
@iain: Не хватает места в поле для комментариев, поэтому разместите его здесь для ясности.
Использование операторов if - неплохая идея! Я знаю, что операторы 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 () не выполняется ошибочно, если трассировка на уровне отладки отключена.
Самый распространенный (и, безусловно, самый эффективный) способ - удалить их с помощью препроцессора, используя что-то вроде этого (простейшая возможная реализация):
#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 . Однако такой поток имеет служебные данные во время выполнения в сборке выпуска.