Как можно перемещать 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()
.