如何创建自己的“cout”和“cerr”类

4
我该如何创建一个行为与std::coutstd::cerr完全相同的类?
我正在编写一个迷你操作系统,这是一个要求,在其中将它们作为模块存在。
代码将如下所示:
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

5
“new”是不必要的。事实上,在解除“myCout”和“myCErr”(这是指针)之前,您提供的示例是无法正常工作的。建议避免使用指针。 - Lightness Races in Orbit
2
找不到任何关于那个问题的答案,我会感激简单的回答。这有点矛盾,不是吗?你的问题似乎太宽泛了。你可以从学习std::cinstd::cout的实现开始。 - πάντα ῥεῖ
1
@Tango_Chaser:“在屏幕上打印”非常模糊。你的“屏幕”是如何实现的?它是什么? - Lightness Races in Orbit
1
@Tango_Chaser:嗯,那就使用std::cout - Lightness Races in Orbit
@Tango_Chaser:抱歉,但那几乎没有意义。你需要理解的一个概念是,std::coutstd::cerrstd::ostream的实例化;它们不是类。它们是实例,预定义为使用_stdout_和_stderr_作为它们的数据接收器。你可以轻松地创建自己的实例,做同样的事情,但它们将是相同的对象,只是名称不同。完全没有意义。如果你真的想给自己更多的工作,你也可以重新发明std::ostream,但那么你又回到了“我发现大部分都是重载<<运算符”。 - Lightness Races in Orbit
显示剩余9条评论
2个回答

2
首先,不要在不熟悉操作并且愿意承担所有风险的情况下这样做。这仅仅是一个思想实验,演示如何将另一个流绑定到 stdout,从而创建第二个 cout。言归正传,下面开始。
如果您想为 stdout 创建另一个流,您需要深入了解编译器的内部结构,并找出它如何定义 coutcerr 和/或 clog。这将位于与编译器相关的位置,很可能不在您预期的位置;例如,在较旧版本的 Visual Studio 上,您需要查看 crt\src 文件夹中的一些文件:
// 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`.

通过这个方法,我们可以派生出自己的stdout流,但这会与编译器有关。

// 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;
}

结果如下...

Hello from char16_t!

如果您想将第二个标准ostream(也称为basic_ostream<char,char_traits<char>>)绑定到stdout,可以使用类似的方法。请注意,由于foutstatic,因此您需要制作自己的filebuf。还要注意的是,这只会引起麻烦,但这并不重要;只需小心数据竞争或任何类似问题即可。
请注意,虽然您可以这样做,但除非您非常了解自己在做什么,并愿意对任何出现问题负责,并愿意花足够的时间深入研究编译器的库和/或代码,以找出它如何实现stdout和默认字符串,否则最好不要这样做。
另请注意,您的代码将与编译器紧密耦合,并且同一编译器的未来版本很有可能会破坏它。例如,据我所知,由于CRT的更改,特别是FILE的更改,此代码将无法在Visual Studio 2015中编译,但我没有深入研究。

1
这些对象是 std::ostream。你可以创建自己的 std::ostream。如何工作取决于数据汇,但一个 std::ostringstream 就足以让你开始测试使用它的代码。
然而,如果你真的想重新发明 std::cout不要 这样做。它的数据汇是魔法文件句柄 stdout,你无法重新创建它,因为它由操作系统提供。你可以创建一个从 std::cout 中窃取该缓冲区的 std::ostream,但这有什么意义呢?

std::ostringstream 可以帮助我在流上使用函数,但最终都会输出到 std::cout 上。那么如何实现自己的 cout 呢? - Tango_Chaser
4
“@Tango_Chaser:‘但最后一切都会传输到 std::cout’”并不是这样的。也许你应该花更多时间去研究它,是吧? - Lightness Races in Orbit
我会加深对该主题的理解,谢谢。 - Tango_Chaser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接