Как добавить отступ для оператора потока - PullRequest
13 голосов
/ 07 марта 2012

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

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

В результате в журнале:

[SomeMember1: foo]
[SomeMember2: bar]

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

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

Спасибо

Ответы [ 4 ]

25 голосов
/ 07 марта 2012

Самое простое решение - проскользнуть потоком фильтрации между 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 в деструкторе.)

1 голос
/ 07 марта 2012

Это можно сделать с помощью пользовательского потокового манипулятора, который сохраняет желаемый уровень отступа в слове внутреннего расширяемого массива потока.Вы можете запросить такое слово, используя функцию 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.Это эффективно позволяет вам добавлять пользовательское состояние в поток (обратите внимание, что это не будет потокобезопасным из коробки).Каждая функция, которая требует отступа, должна добавить запрошенный отступ в поток и вычесть его снова, когда это будет сделано.Вы можете убедиться, что это всегда происходит, используя защитный элемент для добавления / вычитания требуемого отступа (ИМХО, это более элегантно, чем использование манипулятора):

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
}
1 голос
/ 07 марта 2012

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

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

А затем установите его соответствующим образом.

0 голосов
/ 07 марта 2012

Вы можете создать свой собственный класс потока, который имеет переменную отступа, и переопределить endl для этого класса, вставив отступ.

...