- Всегда страйтесь писать программы, которые пишут программы.
@ не помню
Ведётся работа над сложным (ну и не совсем маленьким) проектом на с++. Часто возникает необходимость трассировать работу программы чуть-ли не построчно, и на большом промежутке времени, т.к. баг появляется как следствие роста «снежного кома», и вот когда его радиус достигает критической протяжённости происходит переломный момент, и при его ловле приходится идти назад по развитию ситуации которая привела к этому. Такой сценарий связан со спецификой конкретного продукта. Так вот, при отладке и добавлении модулей проявилось осознание необходимости введения отдельного механизма подробного логирования работы модулей с точностью до стека вызовов (в виде имён функций хотябы, а если они являются методами объекта — то и имя объекта +, возможно, данные его полей):
#0000123 00:05:55 [ProgramName] > [main(2,...)] > [SomeClass, field1=X, ...]::SomeClassFunc(arg1=Y, ...) > [SomeGlobalFunc] > [SomeOtherClass, field=Z]::SomeOtherClass() > Some debug out of constructor...
Хочется реализовать это таким образом, чтобы:
1) Не надо было делать всякие хитрые вступления в начале каждой функции которая использует такой вывод. Лучше вообще без (что видимо нереально для с++), но допускаю тривиальную инициализацию, типа «добавлять в начало каждого вывода ещё и такую строку» где строка чаще всего — имя функции, и возможно значения аргументов, но уж точно избежать ручного прописывания этих данных, т.к. полезно ...
2) Чтобы бывод был визуально идентичным в любом месте программы и переносимым. К примеру первая попытка реализации такой техники была основана на С-препроцессоре, и выглядела примеро так:
#define DEBUG_STREAM (std::cerr)
---< некий другой файл в котором будем использовать дебаг-поток >---
#define _BACKUP_DEBUG_STREAM_ DEBUG_STREAM
#undef DEBUG_STREAM
#define DEBUG_STREAM (_BACKUP_DEBUG_STREAM_ << " [ThisFileClassName, field1=" << field1 << ", field2=" << field2 << "]::")
И дальше используешь вывод следующего вида в любом месте файла:
ThisFileClassName :: Method1(...)
{
DEBUG_STREAM << "::Method1(...) > " << "Some debug info" << std::endl << ...;
DEBUG_STREAM.write(data,size);
}
---< в конце файла >---
#undef DEBUG_STREAM
#define DEBUG_STREAM _BACKUP_DEBUG_STREAM_
// Тут, как многие наверное уже знают, проблема в том, что нельзя переопределять макрос через макрос каким-либо образом связанный с данным ранее. Даже если сделать #undef. (кстати было бы интересно почитать о работе С-препроцессора, порекомендуйте литературу, а то в начале я был уверен что это сработает), и + выводится будет не последовательность вызванных методов, а последовательность препроцессорного парсинга.
// А так же конкретно этот метод не удобен ещё и тем, что нет удобочитаемого метода автоматической добавки хвоста с именем функции (+ ещё бы вывод аргументов или чего ещё), тут его приходится писать руками что не коррелирует с пунктом 1 и вообще /0 этот пункт; либо делать ещё переопределние DEBUG_STREAM #ifdef/#endif в начале и конце функции, что противоречит вобще всякой форме морали и гуманности.
3) Отключение дебаг-режима происходило отключением какого-то глобального макроса и/или изменением некоторой переменной, и в отключенном режиме елись минимум ресурсов (если с помощью макросов, то лучше чтобы под какой-либо оптимизацией код вывода вобще вырезался с корнями. Если на примере первой макросной поделки, то не надо было обрамлять #ifdef/#endif'ами каждый вывод, а заменить корневой DEBUG_STREAM на какой-нибудь класс NullStream с пустыми оператором и write'ом, но вот появление этого паразитного кода в итоговом бинарнике для меня загадка).
Предполагаю что это можно сделать каким-нибудь образом через наследование классов, но это слишком громоздкий подход, и требует очень нетривиальных рукоприкладств. Хотя было бы очень здорово в случае реализации такой же стройной архитектуры, которую можно поднять, к примеру, для иерархии exception'ов.
Актуальная мысль заключается в использовании обёртки fprintf (а может и cerr) которая держит в памяти списко(для дебаг вывода)-стек(для push/pop) структур в которых ... Вот тут проблема... В которых каким-то образом содержатся либо строки форматирование и списки переменных для каждой из строк, либо вообще переменные (в том числе указатели на константный строки) для последовательного запихивать в поток вывода.
Так что вот в таких я раздумьях. У кого есть идеи — будет интересно.