Подтвердить что ты не робот

Как добавить отступ в оператор потока

В нашем проекте мы используем оператор потока С++ (< <) в нашей объектной модели для распечатки легко читаемого формата данных. Упрощенный пример:

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
    oStream << "[SomeMember1: " << iOwnClass._ownMember1 << "]\n";
    oStream << "[SomeMember2: " << iOwnClass._ownMember2 << "]\n";
}

В результате этого при регистрации:

[SomeMember1: foo]
[SomeMember2: bar]

Теперь мы хотим убрать результат этого оператора. Некоторым вызывающим классам может не понравиться такой результат, но нужно добавить 2 пробела в каждой строке. Мы могли бы добавить участника в наш класс, указав отступ, но это не кажется элегантным решением.

Конечно, это не очень большая проблема, но наш журнал будет намного приятнее, если это сработает.

Спасибо

4b9b3361

Ответ 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 для этого класса, вставив отступ.