Как написать вектор ostreams в C ++, который принимает все различные выходные потоки, такие как cout, ostringstream и ofstream - PullRequest
0 голосов
/ 11 сентября 2018

Я пытаюсь реализовать регистратор, который может быть зарегистрирован в нескольких потоках, таких как ostringstream, ofstream и т. Д. Я пытался реализовать такую ​​функцию

void register_stream(std::ostream& a);

Вектор выглядит следующим образом

std::vector<std::ostream> streams;

Поток регистров и перегрузка операторов выглядят следующим образом

void logger::register_stream(std::ostream &a)`

{

    streams.push_back(a);

}

template <typename T>

void logger::operator<<(T const& value)

{

    for (auto stream : streams)

    {

        (stream) << value;

    }

}

Я пытаюсь реализовать регистратор для записи во все зарегистрированные потоки по одному вызову оператора "<<".

Ниже приведен код вызова:

std::ostringstream os;
    std::ofstream f;
    logger l;
    l.register_stream(f);
    l.register_stream(os);
    l << "log this";

Я получаю сообщение об ошибке:

C2280: std::basic_ostream<char,std::char_traits<char>>::basic_ostream(const std::basic_ostream<char,std::char_traits<char>> &): попытка сослаться на удаленную функцию

Любая помощь будет высоко ценится.

Ответы [ 4 ]

0 голосов
/ 12 сентября 2018

«Если вам нужна дополнительная поддержка для управления временем жизни объекта, вам нужно что-то более сложное - например, хранение прокси-объектов, которые знают, владеет ли регистратор данным потоком». - бесполезно

Текущее решение этой проблемы в моем текущем проекте выглядит примерно так:

using ostream_deleter = std::function<void(std::ostream *)>;
using ostream_ptr = std::unique_ptr<std::ostream, ostream_deleter>;

Это позволяет вам сохранить новый объект потока с владельцем, например,

ostream_deleter d{std::default_delete<std::ostream>{}};
ostream_ptr fileStream{new std::ofstream{"/tmp/example.foo"}, std::move(d)};

Обратите внимание, что сначала вы должны создать стертое при удалении типа из соображений безопасности исключений.

Это также позволяет вам использовать глобальные потоки, которые, как известно, переживают ваш регистратор:

ostream_ptr coutStream{&std::cout, [](std::ostream &) {}};

Существует также null_deleter в Boost , если вы хотите больше самодокументируемого синтаксиса.

К сожалению, все еще существует проблема с потоками, которые могут быть внутренне перенаправлены . Действительно, C ++ поддерживает перенаправление вывода (или ввода из) любого потока в другой буфер потока, например,

std::ofstream logStream{"/tmp/my.log"};
const auto oldBuf = std::cout.rdbuf(logStream.rdbuf());
std::cout << "Hello World.\n"; // output redirected to /tmp/my.log
std::cout.rdbuf(oldBuf); // undo redirection

Проблема в том, что время жизни потокового буфера связано с logStream, а не std::cout. Это усложняет любой срок службы rsp. делает полностью общее решение о невозможном. Конечно, дисциплина и соглашение все еще должны сильно помогать, и, по общему признанию, перенаправление потока является довольно неясной и редко используемой функцией.

0 голосов
/ 11 сентября 2018

ostream выполняет форматирование и запись в базовый streambuf.Так что, когда вы используете operator<< несколько раз, он форматирует один и тот же ввод несколько раз без необходимости.Более оптимальный подход состоит в том, чтобы отформатировать один раз, а затем скопировать форматированный вывод в несколько базовых stream s, используя неотформатированную функцию вывода ostream::write.

Удобно иметь интерфейс std::ostream, чтобы вы могли передавать его существующим функциям, ожидающим интерфейс std::ostream.

В основном вам нужна пользовательская реализация streambuf.Написание одного с нуля - хороший опыт обучения, но утомительный и подверженный ошибкам, потому что streambuf интерфейс довольно сложно понять и правильно реализовать.Вместо этого используйте Boost Iostreams Library .

Рабочий пример:

#include <boost/iostreams/stream.hpp>
#include <algorithm>
#include <iostream>
#include <vector>

struct MultiSink {
    using char_type = char;

    struct category
        : boost::iostreams::sink_tag
        , boost::iostreams::flushable_tag
    {};

    std::vector<std::ostream*> sinks_;

    std::streamsize write(char const* s, std::streamsize n) {
        for(auto sink : sinks_)
            sink->write(s, n);
        return n;
    }

    bool flush() {
        for(auto sink : sinks_)
            sink->flush();
        return true;
    }

    void add_sink(std::ostream& s) {
        sinks_.push_back(&s);
    }

    void remove_sink(std::ostream& s) {
        sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), &s), sinks_.end());
    }
};

int main() {
    MultiSink sink;
    boost::iostreams::stream<MultiSink> stream(sink);
    stream->add_sink(std::cout);
    stream->add_sink(std::cerr);

    stream << "Hello, world!" << std::endl;
}

Обратите внимание, что код предполагает, что зарегистрированные потоки переживают многоканальный приемник.Если это не так, вам нужно отменить регистрацию потоков в мульти-приемнике, прежде чем они будут уничтожены.

0 голосов
/ 11 сентября 2018

У вас есть несколько концептуальных проблем для распутывания:

  1. std::cout - это глобальный объект, но std::ostringstream и std::ofstream являются типами .Обсуждение их как взаимозаменяемых выходных данных является ошибкой категории
  2. std::cout - это глобальный объект с временем жизни программы, но любой создаваемый вами std::ofstream экземпляр может иметь другое время жизни.Вам нужно каким-то образом определить, может ли время его жизни закончиться до вашего регистратора (что не беспокоит cout, если ваш регистратор также не имеет времени жизни программы), или сообщить регистратору, что it отвечает за время жизни вашего потока.
  3. с std::vector<std::ostream> streams не может работать, потому что:
    1. копирует потоки по значению, что явно запрещено (см. удаленную копиюконструктор здесь )
    2. , даже если бы это было разрешено, он был бы сломан из-за среза объекта .

С учетом этого, ответ Максима хорош, но не касается времени жизни потока - если это не проблема (вы рады статически гарантировать, что каждый зарегистрированный поток переживет регистратор), то это хорошее решение.

Если вам нужно нужна дополнительная поддержка для управления временем жизни объекта, вам нужно что-то более сложное - например.хранение прокси-объектов, которые знают, владеет ли регистратор данным потоком.

0 голосов
/ 11 сентября 2018

Вы не можете хранить копии своих потоков, они не копируются. Вместо этого вы должны хранить std::reference_wrapper s или указатели.

class logger {
    std::vector<std::reference_wrapper<std::ostream>> streams;
public:
    void register_stream(std::ostream &stream) {
        streams.emplace_back(stream);
    }

    template <typename T>
    void operator<<(T const &value) {
        for (auto &stream : streams) { // note the '&'
            stream.get() << value;
        }
    }
};

или если вы хотите, чтобы ваши звонки можно было связывать, например l << "log" << " this";:

    template <typename T>
    logger & operator<<(T const &value) {
        for (auto &stream : streams) {
            stream.get() << value;
        }
        return *this;
    }
...