Как наследовать от std::ostream?
Я гуглил и просто не могу найти простой ответ на это. И все должно быть просто, как и STL.
Я хочу определить MyOStream, который публично наследует от std::ostream. Допустим, я хочу вызывать foo() каждый раз, когда что-то записывается в мой поток.
class MyOStream : public ostream {
public:
...
private:
void foo() { ... }
}
Я понимаю, что общедоступный интерфейс ostream не является виртуальным, так как это можно сделать? Я хочу, чтобы клиенты могли использовать операторы<< и write() и put() для MyOStream и использовать расширенные возможности моего класса.
6 ответов
Это не простой вопрос, к сожалению. Классы, которые вы должны извлечь, являются basic_
классы, такие как basic_ostream
, Однако деривация из потока может быть не тем, что вам нужно, вы можете вместо этого вывести ее из буфера потока, а затем использовать этот класс для создания экземпляра существующего класса потока.
Вся область сложна, но есть отличная книга об этом Стандартные потоки IOS C++ и локали, на которую я предлагаю вам взглянуть, прежде чем идти дальше.
Я крутил голову вокруг того, как сделать то же самое, и я обнаружил, что на самом деле это не так сложно. По сути, просто создайте подкласс ostream и объекты streambuf и создайте ostream с самим собой в качестве буфера. виртуальный переполнение () из std::streambuf будет вызываться для каждого символа, отправляемого потоку. Чтобы соответствовать вашему примеру, я просто создал функцию foo() и вызвал ее.
struct Bar : std::ostream, std::streambuf
{
Bar() : std::ostream(this) {}
int overflow(int c)
{
foo(c);
return 0;
}
void foo(char c)
{
std::cout.put(c);
}
};
void main()
{
Bar b;
b<<"Look a number: "<<std::hex<<29<<std::endl;
}
о, и игнорируйте тот факт, что основная функция не является реальной главной функцией. Он находится в пространстве имен, вызываемом из другого места;p
Еще один способ добиться подобного эффекта - использовать шаблон и композицию.
class LoggedStream {
public:
LoggedStream(ostream& _out):out(_out){}
template<typename T>
const LoggedStream& operator<<(const T& v) const {log();out << v;return *this;}
protected:
virtual void log() = 0;
ostream& out;
};
class Logger : LoggedStream {
void log() { std::cerr << "Printing" << std::endl;}
};
int main(int,char**) {LoggedStream(std::cout) << "log" << "Three" << "times";}
Я не знаю, является ли это правильным решением, но я унаследовал от std:: ostream таким образом. Он использует буфер, унаследованный от std::basic_streambuf, и получает 64 символа за раз (или меньше, если сбрасывается) и отправляет их в универсальный метод putChars(), где выполняется фактическая обработка данных. Это также демонстрирует, как предоставить пользовательские данные.
#include <streambuf>
#include <ostream>
#include <iostream>
//#define DEBUG
class MyData
{
//example data class, not used
};
class MyBuffer : public std::basic_streambuf<char, std::char_traits<char> >
{
public:
inline MyBuffer(MyData data) :
data(data)
{
setp(buf, buf + BUF_SIZE);
}
protected:
// This is called when buffer becomes full. If
// buffer is not used, then this is called every
// time when characters are put to stream.
inline virtual int overflow(int c = Traits::eof())
{
#ifdef DEBUG
std::cout << "(over)";
#endif
// Handle output
putChars(pbase(), pptr());
if (c != Traits::eof()) {
char c2 = c;
// Handle the one character that didn't fit to buffer
putChars(&c2, &c2 + 1);
}
// This tells that buffer is empty again
setp(buf, buf + BUF_SIZE);
return c;
}
// This function is called when stream is flushed,
// for example when std::endl is put to stream.
inline virtual int sync(void)
{
// Handle output
putChars(pbase(), pptr());
// This tells that buffer is empty again
setp(buf, buf + BUF_SIZE);
return 0;
}
private:
// For EOF detection
typedef std::char_traits<char> Traits;
// Work in buffer mode. It is also possible to work without buffer.
static const size_t BUF_SIZE = 64;
char buf[BUF_SIZE];
// This is the example userdata
MyData data;
// In this function, the characters are parsed.
inline void putChars(const char* begin, const char* end){
#ifdef DEBUG
std::cout << "(putChars(" << static_cast<const void*>(begin) <<
"," << static_cast<const void*>(end) << "))";
#endif
//just print to stdout for now
for (const char* c = begin; c < end; c++){
std::cout << *c;
}
}
};
class MyOStream : public std::basic_ostream< char, std::char_traits< char > >
{
public:
inline MyOStream(MyData data) :
std::basic_ostream< char, std::char_traits< char > >(&buf),
buf(data)
{
}
private:
MyBuffer buf;
};
int main(void)
{
MyData data;
MyOStream o(data);
for (int i = 0; i < 8; i++)
o << "hello world! ";
o << std::endl;
return 0;
}
Метод Бена работает, но в реальном применении это ужасный метод. В его методе буфер в памяти вообще отсутствует, и вы, по сути, выводите каждый символ.
Для достижения цели необходимо создать два типа занятий.
Класс filebuf, производный от std::streambuf с виртуальными методами.
sync(), overflow(),xsputn(),seekoff(),seekpos()
переопределил. Он также должен иметь соответствующую буферизацию.Класс потока, производный от std::basic_ostream. Например, у него должен быть закрытый член в качестве вашего индивидуального файлового буфера.
buf
и вызовите std::basic_ios&lt;CharT,Traits&gt;::init, напримерthis->init(&buf)
в его конструкторе. Вам НЕ нужно определять какие-либо другие методы, потому чтоstd::basic_ostream
справится с этим за вас. Завершив начальный этап,this->rdbuf()
вернется&buf
.
Базовый стек вызовов выглядит следующим образом
Basic_ostream& оператор<<:
, который будет вызыватьBasic_ostream& put(char_type ch)
rdbuf()->sputc
, который вызоветrdbuf()->overflow
Basic_ostream& write(const char_type* s, std::streamsize count)
rdbuf()->sputn
, который вызоветrdbuf()->xsputn
pos_type сказатьp()
rdbuf()->pubseekoff
, который вызоветrdbuf()->seekoff
Basic_ostream& seekp(off_type off, std::ios_base::seekdir dir)
rdbuf()->pubseekpos
, который вызоветrdbuf()->seekpos
Basic_ostream& флеш()
rdbuf()->pubsync
, который вызоветrdbuf()->sync
Вот пример, полный пример здесь https://github.com/luohancfd/mpistream
В большинстве сценариев вам нужно изменить только функции на open_file, close_file, seek_pos, Tell_pos и write_data. Путем настройкиbuf_size
, вы можете получить значительное улучшение производительности.
// GPL v3.0
class MPI_filebuf : public std::streambuf {
public:
using Base = std::streambuf;
using char_type = typename Base::char_type;
using int_type = typename Base::int_type;
using pos_type = typename Base::pos_type;
using off_type = typename Base::off_type;
private:
static const std::streamsize buf_size = BUFSIZ;
char buffer_[buf_size];
MPI_File fhw;
bool opened;
/**
* @brief Always save one extra space in buffer_
* for overflow
*/
inline void reset_ptr() { setp(buffer_, buffer_ + buf_size - 1); }
protected:
/**
* @brief For output streams, this typically results in writing the contents
* of the put area into the associated sequence, i.e. flushing of the output
* buffer.
*
* @return int Returns 0 on success, -1 otherwise. The base class
* version returns 0.
*/
inline int sync() override {
int ret = 0;
if (pbase() < pptr()) {
const int_type tmp = overflow();
if (traits_type::eq_int_type(tmp, traits_type::eof())) {
ret = -1;
}
}
return ret;
}
/**
* @brief Write overflowed chars to file, derived from std::streambuf
* It's user's responsibility to maintain correct sequence of
* output as we are using shared file pointer
*
* @param ch
* @return int_type Returns unspecified value not equal to Traits::eof() on
* success, Traits::eof() on failure.
*/
inline int_type overflow(int_type ch = traits_type::eof()) override {
// https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/fstream.tcc
int_type ret = traits_type::eof();
const bool testeof = traits_type::eq_int_type(ch, ret);
if (pptr() == nullptr) {
reset_ptr();
if (!testeof) {
ret = sputc(ch);
}
} else {
if (!testeof) {
*pptr() = traits_type::to_char_type(ch);
pbump(1);
}
if (write(pbase(), pptr() - pbase())) {
ret = traits_type::not_eof(ch);
}
reset_ptr();
}
return ret;
}
/**
* @brief Writes \c count characters to the output sequence from the character
* array whose first element is pointed to by \c s . Overwrite this function
* to achieve no_buffered I/O
*
* @param s
* @param n
* @return std::streamsize
*/
inline std::streamsize xsputn(const char_type *s,
std::streamsize n) override {
std::streamsize bufavail = epptr() - pptr();
std::streamsize ret = n;
// fill buffer up to "buf_size"
std::streamsize nfill = std::min(n, bufavail + 1);
std::copy(s, s + nfill, pptr());
pbump(nfill); // if nfill == bufavail+1, pptr() == epptr()
if (nfill == bufavail + 1) {
// equiv: bufavail + 1<= n
if (!write(pbase(), pptr() - pbase())) {
ret = -1;
} else {
reset_ptr();
s += nfill;
n -= nfill;
/*
repeatedly write every chunk until there is
less data than buf_size - 1
*/
while (n >= buf_size - 1) {
write(s, buf_size);
s += buf_size;
n -= buf_size;
}
std::copy(s, s + n, pptr());
pbump(n);
}
}
return ret;
}
/**
* @brief Sets the position indicator of the input and/or output
* sequence relative to some other position. It will flush
* the internal buffer to the file
* @note This function is collective, which means seekp(), tellp()
* need to be called by all processors
*
* @param off relative position to set the position indicator to.
* @param dir defines base position to apply the relative offset to.
* It can be one of the following constants: beg, cur, end
* @param which
* @return pos_type The resulting absolute position as defined by the position
* indicator.
*/
inline pos_type
seekoff(off_type off, std::ios_base::seekdir dir,
__attribute__((__unused__))
std::ios_base::openmode which = std::ios_base::out) override {
int ret = pos_type(off_type(-1));
if (is_open()) {
int whence;
if (dir == std::ios_base::beg)
whence = MPI_SEEK_SET;
else if (dir == std::ios_base::cur)
whence = MPI_SEEK_CUR;
else
whence = MPI_SEEK_END;
sync(); /*!< write data to the file */
if (off != off_type(0) || whence != SEEK_CUR) {
if (MPI_File_seek_shared(fhw, off, whence)) {
// fail to seek
return ret;
}
}
MPI_Offset tmp;
MPI_File_get_position_shared(fhw, &tmp);
ret = pos_type(tmp);
}
return ret;
}
inline pos_type seekpos(pos_type pos, __attribute__((__unused__))
std::ios_base::openmode which =
std::ios_base::out) override {
return seekoff(off_type(pos), std::ios_base::beg);
}
/**
* @brief Method doing the real writing. It moves the data in the
* internal buffer to the file
*
* @param pbeg
* @param nch
* @return true Succeed to write
* @return false Fail to write
*/
inline bool write(const char_type *pbeg, std::streamsize nch) {
return nch == 0 ||
!MPI_File_write_shared(fhw, pbeg, nch, MPI_CHAR, MPI_STATUS_IGNORE);
}
public:
MPI_filebuf() : buffer_{}, opened(false) {
setp(buffer_, buffer_ + buf_size - 1);
}
virtual ~MPI_filebuf() override {
if (opened)
close();
};
/**
* @brief return nullptr if fail
*
* @param file_name
* @return MPI_filebuf*
*/
MPI_filebuf *open(const char file_name[]);
inline bool is_open() const { return opened; }
MPI_filebuf *close() {
sync();
return MPI_File_close(&fhw) ? nullptr : this;
}
};
/* ---------------------------------------------------------------------- */
class mpistream : public std::basic_ostream<char> {
public:
// Types
using Base = std::basic_ostream<char>;
using int_type = typename Base::int_type;
using char_type = typename Base::char_type;
using pos_type = typename Base::pos_type;
using off_type = typename Base::off_type;
using traits_type = typename Base::traits_type;
// Non-standard types:
using filebuf_type = MPI_filebuf;
using ostream_type = Base;
private:
filebuf_type filebuf;
public:
mpistream() : ostream_type(), filebuf() { this->init(&filebuf); }
mpistream(const char file_name[]) : ostream_type(), filebuf() {
this->init(&filebuf);
open(file_name);
}
mpistream(const mpistream &) = delete;
mpistream(mpistream &&__rhs)
: ostream_type(std::move(__rhs)), filebuf(std::move(__rhs.filebuf)) {
ostream_type::set_rdbuf(&filebuf);
}
~mpistream() {}
inline void open(const char file_name[]) {
if (filebuf.open(file_name) == nullptr) {
this->setstate(std::ios_base::failbit);
} else {
this->clear();
}
}
inline bool is_open() const { return filebuf.is_open(); }
inline void close() {
if (!filebuf.close()) {
this->setstate(ios_base::failbit);
}
}
};
Композиция, а не наследство. Ваш класс содержит, "оборачивает" ostream& и передает его (после вызова foo()).