Как можно перемещать std:: ostream?

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

Основная задача состоит в том, чтобы иметь функцию factory, которая принимает URI и что-то возвращает, позвольте ей, omstream (выводить подвижный поток), который может использоваться как std::ostream:

omstream stream_factory(std::string const& uri);
void     process(std::ostream& out);

int main(int ac, char* av[]) {
    omstream destination{ stream_factory(ac == 2? av[1]: "example.txt") };
    process(destination);
}

omstream будет отвечать за правильное перемещение объекта:

class omstream
    : public std::ostream {
    // suitable members
public:
    omstream(/* suitable constructor arguments */);
    omstream(omstream&& other) // follow recipe of 27.9.1.11 [ofstream.cons] paragraph 4
        : std:ios(std::move(other))
        , std::ostream(std::move(other))
        // move any members {
        this->set_rdbuf(/* get the stream buffer */);
    }
    // other helpful or necessary members
};

Вопрос в том, что нужно для реализации omstream (или даже соответствующего шаблона класса basic_omstream)?

Ответ 1

У тебя почти все правильно. Ваш пример - это перемещение, создающее базу ios дважды. Вы должны перемещать только базовый класс direct. И предположим, что есть член streambuf, переместите это тоже:

class omstream
    : public std::ostream {
    // suitable members
public:
    omstream(/* suitable constructor arguments */);
    omstream(omstream&& other) // follow recipe of 27.9.1.11 [ofstream.cons] paragraph 4
        : std: ostream(std::move(other)),
        // move any members {
        this->set_rdbuf(/* install the stream buffer */);
    }
    // other helpful or necessary members
};

Я изменил "get" на "install" в комментарии set_rdbuf. Обычно это устанавливает указатель на член streambuf в базовый класс ios.

Текущая неортодоксальная конструкция элементов перемещения и свопинга istream/ostream была настроена так, чтобы сделать перемещение и замену членов производных классов (таких как ofstream и omstream) более интуитивно понятными. Рецепт:

Переместите базу и элементы, а в конструкторе перемещения установите rdbuf.

Именно встроенный rdbuf является усложняющим фактором для всей иерархии.

Ответ 2

Код, указанный в ответе Говарда, представляет собой проект (на основе проекта, опубликованного в вопросе). Ответ Говарда помог решить запутанную проблему с базовым классом virtual std::ios: базовый класс должен быть сконфигурирован по умолчанию при перемещении производного потока, так как часть потока std::ios будет явно перемещаться с помощью перемещения std::ostream конструктор с использованием std::ios::move(). Этот ответ просто заполняет отсутствующие бит.

В приведенной ниже реализации содержится указатель на буфер потока, который, как правило, ожидается, что он будет находиться в куче, и будет выпущен после уничтожения с помощью std::unique_ptr<...>. Поскольку может быть желательно вернуть std::omstream буфер потока долгоживущего потока, например, std::cout, std::unique_ptr<...> настроен на использование делетера, который ничего не может сделать, если omstream собственный буфер потока.

#include <ostream>
#include <memory>
#include <utility>

template <typename cT, typename Traits = std::char_traits<cT>>
class basic_omstream
    : public std::basic_ostream<cT, Traits>
{
    using deleter = void (*)(std::basic_streambuf<cT, Traits>*);

    static void delete_sbuf(std::basic_streambuf<cT, Traits>* sbuf) {
        delete sbuf;
    }
    static void ignore_sbuf(std::basic_streambuf<cT, Traits>*) {
    }
    std::unique_ptr<std::basic_streambuf<cT, Traits>, deleter> m_sbuf;
public:
    basic_omstream()
        : std::basic_ios<cT, Traits>()
        , std::basic_ostream<cT, Traits>(nullptr)
        , m_sbuf(nullptr, &ignore_sbuf) {
    }
    basic_omstream(std::basic_streambuf<cT, Traits>* sbuf,
                   bool owns_streambuf)
        : std::basic_ios<cT, Traits>()
        , std::basic_ostream<cT, Traits>(sbuf)
        , m_sbuf(sbuf, owns_streambuf? &delete_sbuf: &ignore_sbuf) {
        this->set_rdbuf(this->m_sbuf.get());
    }
    basic_omstream(basic_omstream&& other)
        : std::basic_ios<cT, Traits>() // default construct ios!
        , std::basic_ostream<cT, Traits>(std::move(other))
        , m_sbuf(std::move(other.m_sbuf)) {
        this->set_rdbuf(this->m_sbuf.get());
    }
    basic_omstream& operator=(basic_omstream&& other) {
        this->std::basic_ostream<cT, Traits>::swap(other);
        this->m_sbuf.swap(other.m_sbuf);
        this->set_rdbuf(this->m_sbuf.get());
        return *this;
    }
};

typedef basic_omstream<char>    omstream;
typedef basic_omstream<wchar_t> womstream;

Использование std::ofstream или std::ostringstream для инициализации omstream не работает, если соответствующий поток не переживает omstream. В общем случае будет выделен соответствующий буфер потока. Класс omstream можно, например, использовать как в приведенном ниже коде, который создает поток на основе URI, заданного подходящей функции factory:

#include <iostream>
#include <sstream>
#include <fstream>

omstream make_stream(std::string const& uri) {
    if (uri == "stream://stdout") {
        return omstream(std::cout.rdbuf(), false);
    }
    else if (uri == "stream://stdlog") {
        return omstream(std::clog.rdbuf(), false);
    }
    else if (uri == "stream://stderr") {
        return omstream(std::cerr.rdbuf(), false);
    }
    else if (uri.substr(0, 8) == "file:///") {
        std::unique_ptr<std::filebuf> fbuf(new std::filebuf);
        fbuf->open(uri.substr(8), std::ios_base::out);
        return omstream(fbuf.release(), true);
    }
    else if (uri.substr(0, 9) == "string://") {
        return omstream(new std::stringbuf(uri.substr(9)), true);
    }
    throw std::runtime_error("unknown URI: '" + uri + "'");
}

int main(int ac, char* av[])
{
    omstream out{ make_stream(ac == 2? av[1]: "stream://stdout") };
    out << "hello, world\n";
}

Если существуют другие буферы потоков, которые могут быть созданы из URI, они могут быть добавлены к функции make_stream().