Как добавить отступ в оператор потока
В нашем проекте мы используем оператор потока С++ (< <) в нашей объектной модели для распечатки легко читаемого формата данных. Упрощенный пример:
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
oStream << "[SomeMember1: " << iOwnClass._ownMember1 << "]\n";
oStream << "[SomeMember2: " << iOwnClass._ownMember2 << "]\n";
}
В результате этого при регистрации:
[SomeMember1: foo]
[SomeMember2: bar]
Теперь мы хотим убрать результат этого оператора. Некоторым вызывающим классам может не понравиться такой результат, но нужно добавить 2 пробела в каждой строке. Мы могли бы добавить участника в наш класс, указав отступ, но это не кажется элегантным решением.
Конечно, это не очень большая проблема, но наш журнал будет намного приятнее, если это сработает.
Спасибо
Ответы
Ответ 1
Самое простое решение - пропустить фильтрацию streambuf между
ostream
и фактический streambuf. Что-то вроде:
class IndentingOStreambuf : public std::streambuf
{
std::streambuf* myDest;
bool myIsAtStartOfLine;
std::string myIndent;
std::ostream* myOwner;
protected:
virtual int overflow( int ch )
{
if ( myIsAtStartOfLine && ch != '\n' ) {
myDest->sputn( myIndent.data(), myIndent.size() );
}
myIsAtStartOfLine = ch == '\n';
return myDest->sputc( ch );
}
public:
explicit IndentingOStreambuf(
std::streambuf* dest, int indent = 4 )
: myDest( dest )
, myIsAtStartOfLine( true )
, myIndent( indent, ' ' )
, myOwner( NULL )
{
}
explicit IndentingOStreambuf(
std::ostream& dest, int indent = 4 )
: myDest( dest.rdbuf() )
, myIsAtStartOfLine( true )
, myIndent( indent, ' ' )
, myOwner( &dest )
{
myOwner->rdbuf( this );
}
virtual ~IndentingOStreambuf()
{
if ( myOwner != NULL ) {
myOwner->rdbuf( myDest );
}
}
};
Чтобы вставить, просто создайте экземпляр streambuf:
IndentingOStreambuf indent( std::cout );
// Indented output...
Когда indent
выходит за пределы области видимости, все возвращается в нормальное состояние.
(Для ведения журнала у меня есть тот, который немного сложнее:
LoggingOStreambuf
принимает __FILE__
и __LINE__
в качестве аргументов, устанавливает
myIndent
в форматированную строку с этими аргументами плюс время
штамп, сбрасывает его до строки отступа после каждого выхода, собирает
весь вывод в std::ostringstream
и выводит его атомарно
до myDest
в деструкторе.)
Ответ 2
Не так хороший способ сделать это - добавить глобальную переменную, которая сообщает отступы. Что-то вроде этого:
std::string OwnClassIndentation;
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
oStream << "[SomeMember1:" << OwnClassIndentation << iOwnClass._ownMember1 << "]\n";
oStream << "[SomeMember2:" << OwnClassIndentation << iOwnClass._ownMember2 << "]\n";
}
И затем установите его соответствующим образом.
Ответ 3
Это можно сделать с помощью настраиваемого манипулятора потока, который сохраняет желаемый уровень отступов словом внутреннего расширяемого массива потока. Вы можете запросить такое слово, используя функцию ios_base::xalloc
. Эта функция даст вам индекс вашего слова. Вы можете получить доступ к нему, используя ios_base::iword
. Один из способов реализовать это:
struct indent {
indent(int level) : level(level) {}
private:
friend std::ostream& operator<<(std::ostream& stream, const indent& val);
int level;
};
std::ostream& operator<<(std::ostream& stream, const indent& val) {
for(int i = 0; i < val.level; i++) {
stream << " ";
}
return stream;
}
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
oStream << indent(oStream.iword(index)) << "[SomeMember1: " <<
iOwnClass._ownMember1 << "]\n";
oStream << indent(oStream.iword(index)) << "[SomeMember2: " <<
iOwnClass._ownMember2 << "]\n";
}
Вам нужно выяснить, где хранить index
. Это позволяет вам добавить пользовательское состояние в поток (обратите внимание, что это не будет потокобезопасным из коробки). Каждая функция, требующая отступов, должна добавить запрошенный отступ в поток и вычесть его снова, когда это будет сделано. Вы можете убедиться, что это всегда происходит с помощью защитника для добавления/вычитания желаемого отступа (IMHO это более элегантно, чем использование манипулятора):
class indent_guard {
public:
indent_guard(int level, std::ostream& stream, int index)
: level(level),
stream(stream),
index(index)
{
stream.iword(index) += level;
}
~indent_guard() {
stream.iword(index) -= level;
}
private:
int level;
std::ostream& stream;
int index;
};
Вы можете использовать его следующим образом:
void some_func() {
indent_guard(2, std::cout, index);
// all output inside this function will be indented by 2 spaces
some_func(); // recursive call - output will be indented by 4 spaces
// here it will be 2 spaces again
}
Ответ 4
Вы можете создать свой собственный класс потока с переменной отступа и переопределить endl для этого класса, вставив отступ.