Reputation: 331
How do I create my own class that behaves exactly like: std::cout
& std::cerr
.
I am writing a mini operating system and this is a requirement, having these as modules there.
The code will look like:
myNewCoutClass myCout; // create cout behavioral class
myNewCerrClass myCerr; // create cerr behavioral class
myCout << someString << endl; // prints the string
myCerr << someString << endl; // prints the string as error
Upvotes: 4
Views: 3786
Reputation: 4339
First things first, don't do this unless you know very well what you're doing, and are willing to take all of the risks involved. It's just an example of how one could bind another stream to stdout
, in effect creating a second cout
, as a thought experiment. That said, here we go.
If you want to create another stream for stdout
, you have to take a good look at your compiler's deep, dark innards, and find out how it defines cout
, cerr
, and/or clog
. This will be in a compiler-dependent location, and most likely not where you expect; for example, on older versions of Visual Studio, you would have to look at a few of the files in the crt\src
folder:
// Visual Studio 2010 implementation of std::cout.
// Irrelevant parts omitted.
// cout.cpp
__PURE_APPDOMAIN_GLOBAL static filebuf fout(_cpp_stdout);
#if defined(_M_CEE_PURE)
__PURE_APPDOMAIN_GLOBAL extern ostream cout(&fout);
#else
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 ostream cout(&fout);
#endif
struct _Init_cout
{
__CLR_OR_THIS_CALL _Init_cout()
{
_Ptr_cout = &cout;
if (_Ptr_cin != 0)
_Ptr_cin->tie(_Ptr_cout);
if (_Ptr_cerr != 0)
_Ptr_cerr->tie(_Ptr_cout);
if (_Ptr_clog != 0)
_Ptr_clog->tie(_Ptr_cout);
}
};
__PURE_APPDOMAIN_GLOBAL static _Init_cout init_cout;
// stdio.h
#define _INTERNAL_BUFSIZ 4096
// ...
#define _IOB_ENTRIES 20
// ...
#ifndef _STDSTREAM_DEFINED
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
#define _STDSTREAM_DEFINED
#endif /* _STDSTREAM_DEFINED */
// _file.c
char _bufin[_INTERNAL_BUFSIZ];
FILE _iob[_IOB_ENTRIES] = {
/* _ptr, _cnt, _base, _flag, _file, _charbuf, _bufsiz */
/* stdin (_iob[0]) */
{ _bufin, 0, _bufin, _IOREAD | _IOYOURBUF, 0, 0, _INTERNAL_BUFSIZ },
/* stdout (_iob[1]) */
{ NULL, 0, NULL, _IOWRT, 1, 0, 0 },
/* stderr (_iob[3]) */
{ NULL, 0, NULL, _IOWRT, 2, 0, 0 },
};
_CRTIMP FILE * __cdecl __iob_func(void)
{
return _iob;
}
// `__PURE_APPDOMAIN_GLOBAL` is an internal macro that can generally be ignored.
// `_CRTIMP` is an internal macro that can generally be ignored.
// `_CRTDATA2` is an internal macro that can generally be ignored.
// `__CLR_OR_THIS_CALL` is a calling convention macro that expands to either
// `__clrcall` or `__thiscall`.
From this, we can derive our own stream for stdout
, although it'll be compiler-dependent.
// Visual Studio 2010 user-created char16_t cout.
// Note that in VStudio 2010, char16_t is actually a typedef for unsigned short.
#include <iostream>
#include <fstream>
#include <string>
#include <codecvt>
#define _cpp_stdout (&(__iob_func())[1])
typedef std::basic_filebuf<char16_t, std::char_traits<char16_t>> filebuf_c16;
typedef std::basic_ostream<char16_t, std::char_traits<char16_t>> ostream_c16;
int main() {
filebuf_c16 f16out(_cpp_stdout);
ostream_c16 c16out(&f16out);
// It really should be tied to the other stdin/stdout/stderr streams,
// but this is a simple program where it won't be a problem.
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
std::string u8tmp = "Hello from char16_t!";
std::u16string u16str = converter.from_bytes(u8tmp);
c16out << u16str << std::endl;
}
And the results...
Hello from char16_t!
If you wanted to tie a second standard ostream
(a.k.a. a basic_ostream<char, char_traits<char>>
) to stdout
, you could use something similar. Note that as fout
is static
, you would need to make your own filebuf
. Also note that this is just asking for trouble, but that's beside the point; just be careful of data races, or anything of the sort.
Note that while you can do this, unless you know very well what you're doing, are willing to take responsibility for anything that goes wrong, and are willing to spend enough time delving into your compiler's library and/or code to find out how exactly it implements stdout
and the default strings, you very well shouldn't do it.
Also note that your code will be tightly coupled to the compiler, and there's a very high probability that future versions of the same compiler may break it. For example, to my knowledge, this code won't compile with Visual Studio 2015 because of changes to the CRT (specifically, I believe it's because of changes to FILE
, but I didn't look into it).
Upvotes: 2
Reputation: 385174
These objects are std::ostream
s. You can create your own std::ostream
. Precisely how that'll work depends entirely on the data sink, which you haven't specified, but an std::ostringstream
will be enough to get you started on testing the code that uses it.
However, if you literally wish to reinvent std::cout
, don't. Its data sink is the magic file handle stdout, which you can't recreate as it's provided by the OS. You could create a std::ostream
that steals that buffer from std::cout
but what's the point?
Upvotes: 1