Начать ввод (cin) в файл журнала (или засорить) - PullRequest
1 голос
/ 15 июня 2009

Я ищу способ ветвления (т. Е.) Ввода, считанного из istream (cin, в моем случае), в файл журнала (clog / ofstream / etc), при этом все еще используя вход для обработки.

Я читал о boost :: tee_device, и он очень похож на мои требования. К сожалению, он реализован как ostream и, таким образом, решает аналогичную проблему с «другой стороны трубы».

Я попытался написать класс istream (адаптер), который перенаправляет входные функции в упакованный поток ввода (cin), а также отправляет то, что было прочитано, в файл журнала.

Это прекрасно работает для базовых типов, которые вызывают оператор >> (...) напрямую, однако у меня возникли проблемы с более сложным использованием входного потока, например, для оператора >> (std :: string ) и функция getline std :: string.

Есть ли более простой способ сделать это (возможно, с помощью манипуляции с rdbuf ())?

Спасибо!

Редактировать: я мог бы повсеместно изменить свой код на что-то вроде: cin >> value; засорение << значение; - но это было бы значительным и безобразным изменением. Я также предпочел бы иметь простой способ отключить регистрацию. Таким образом, я хотел бы получить способ смоделировать это как «фильтр» istream, а затем просто заменить все ссылки на cin этим «логгером» istream. </p>

Идеальное решение:

class log_istream : public std::istream
{
public:
    log_istream( std::istream & in , std::ostream & out );

    /* ... istream forwarding functions ... */

private:
    std::istream & in_;
    std::ostream & out_;     
};

int main() {
    log_istream logger( std::cin , std::ofstream("logfile.out") );

    logger >> value; // this implies infile >> value and logfile << value
    getline(logger,my_string); // this also implies logfile.writeline(value)
    // etc
}

и т.д.

Ответы [ 4 ]

3 голосов
/ 16 июня 2009

Используя Boost.IOStreams, вы можете определить входной фильтр, который записывает то, что он читает, в засорение. Что-то вроде:

(предупреждение: непроверенный код впереди)

class LoggingInputFilter : public multichar_input_filter {
public:
    template<typename Source>
    std::streamsize read(Source& Src, char* S, std::streamsize N)
    {
        streamsize result = read(Src, S, N);
        if (result == -1){
            return result;
        }

        if (std::clog.write(S, result)){
            return result;
        }

        return -1;
    }
};

Цепочка с std :: cin:

LoggingInputFilter cin_logger;
filtering_stream logged_cin(cin_logger);
logged_cin.push(std::cin);

и используйте logged_cin вместо std :: cin

Edit: или работать на уровне streabuf, так что ваш код все еще использует std :: cin:

LoggingInputFilter cin_logger;
filtering_streambuf logged_cin(cin_logger);
logged_cin.push(std::cin.rdbuf());
std::cin.rdbuf(logged_cin);
1 голос
/ 31 июля 2009

Окончательный ответ:

#ifndef TEE_ISTREAM_H_
#define TEE_ISTREAM_H_

/*****************************************************************************/

#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/invert.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/tr1/memory.hpp>
#include <iostream>

/*****************************************************************************/

namespace bio = boost::iostreams;

/*****************************************************************************/

class tee_source : public bio::source {
public:
    tee_source( std::istream & in, const std::string & filename )
        : in_(in), log_file_(filename, std::ios::app), tee_(bio::tee(log_file_), 1)
    { }

    std::streamsize read(char* s, std::streamsize n)
    {
        return tee_.read(in_,s,n);
    }

private:
    std::istream &                               in_;
    bio::file                                    log_file_;
    bio::inverse< bio::tee_filter< bio::file > > tee_;
};

/*****************************************************************************/

typedef bio::filtering_istream                tee_istream_t;
typedef std::tr1::shared_ptr< tee_istream_t > tee_istream_ptr_t;

/*****************************************************************************/

inline tee_istream_ptr_t make_tee_istream( std::istream & in, const std::string & filename )
{
    return tee_istream_ptr_t( new tee_istream_t( tee_source( in , filename ), 0 ) );
}

/*****************************************************************************/

#endif
1 голос
/ 16 июня 2009

Я нашел простое решение:

Boost :: iostreams обеспечивает инверсию между фильтрами источника / приемника.

Хотя tee_filter смоделирован как приемник, вы можете инвертировать () его в источник, и он все равно будет «тройник» фильтровать в указанный приемник:

    boost::iostreams::file log_file("sample.txt", std::ios::trunc); // or std::ios::app

    // Single parameter tee() function returns a tee_filter , and invert() inverts that filter
    boost::iostreams::filtering_istream in(
            boost::iostreams::invert(
                    boost::iostreams::tee(log_file)));

Таким образом, я регистрирую все отфильтрованные данные.

Производительность не проблема, но если кто-то заметит какие-либо красные флажки, мне будет очень интересно. Благодаря.

0 голосов
/ 02 января 2013

Хороший простой / короткий способ (мне нужно последнее повышение, я думаю, и -std = c ++ 11) - благодаря @mmocny, @ Éric Malenfant и разработчикам Boost lib.

Эта демонстрационная программа записывает в "tmp.log" все разговоры, которые вы ведете на std :: cin и std :: cout:

#include <iostream>

#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/file.hpp>

namespace bio = boost::iostreams;

int main(int, char**)
{
    // Logging
    bio::filtering_ostream log;
    log.push(bio::file_sink("tmp.log", std::ios_base::app));

    // Tee filter instance (will be copied into each filter stream)
    const bio::tee_filter<std::ostream> teeFilter(log);

    // std::out tee
    bio::filtering_ostream out;
    out.push(teeFilter);
    out.push(std::cout);

    // std::in tee
    bio::filtering_istream in;
    in.push(teeFilter, 0); // If you don't pass 0 for buffer size, on std::cin it'll try to read 4096 chars and basically be useless
    in.push(std::cin, 0);

    out << "What is your name ?" << std::endl << std::flush;
    std::string name;
    getline(in, name);
    out << "Hello " << name << std::endl << std::flush;
}
...