扩展 std::cout

ano*_*non 0 c++ winapi cout ostream streambuf

std::cout我想扩展使用我自己的控制台/cout 包装类的用法。

理想情况下,我会有 2 个 ostream,一个用于常规打印,另一个用于附加新行。

std::ostream Write;
Write << "Hello, I am " << 99 << " years old.";
Run Code Online (Sandbox Code Playgroud)

印刷Hello, I am 99 years old.

std::ostream WriteLine;
WriteLine << "Hello, I am " << 99 << " years old.";
Run Code Online (Sandbox Code Playgroud)

打印Hello, I am 99 years old.\n(一个实际的新行,而不仅仅是转义)

然后,我想扩展它以具有错误流(例如Error和),该错误流在消息之前添加前缀并以不同的颜色打印。ErrorLine"ERROR: "

我知道我必须创建自己的流来添加此功能,并且我遵循C++ cout 并使用前缀作为前缀std::cout,这几乎是我想要的,但不完全是。我不知道如何在流的末尾添加新行,并且前缀不可靠,特别是当我要执行多个打印语句时。

我还应该提到我不想使用重载运算符来实现这种效果,因为我希望能够以菊花链方式连接事物。

什么不起作用

如果我WriteLine >> "First";这样做,WriteLine << "Second";我会得到奇怪的结果,例如SecondFirst\nSecond\nFirst。我理想的输出是First\nSecond\n. 我认为这是由于没有正确关闭/刷新/重置流,但我没有尝试让它可靠地工作。

我可以让它适用于单个语句,但是一旦我添加了另一个打印语句,我尝试打印的内容就会切换顺序,后/前修复不会添加到正确的位置,否则我会结束垃圾。

我不关心 wchars,因为我们总是只需要单个字符的单个字节。此外,我们将仅在 Windows 10 上工作。

这是我到目前为止所拥有的:

控制台.h

#include <windows.h>
#include <iostream>
#include <sstream>
#include <string>

class Console {
    using Writer = std::ostream;
    Console() {}
    static const char newline = '\n';

    class error_stream: public std::streambuf {
    public:
        error_stream(std::streambuf* s) : sbuf(s) {}
        ~error_stream() { overflow('\0'); }
    private:
        typedef std::basic_string<char_type> string;

        int_type overflow(int_type c) {

            if(traits_type::eq_int_type(traits_type::eof(), c))
                return traits_type::not_eof(c);
            switch(c) {
            case '\n':
            case '\r':
            {
                SetColor(ConsoleColor::Red);
                prefix = "ERROR: ";
                buffer += c;
                if(buffer.size() > 1)
                    sbuf->sputn(prefix.c_str(), prefix.size());
                int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
                buffer.clear();
                SetColor(ConsoleColor::White);

                return rc;
            }
            default:
                buffer += c;
                return c;
            }
        }

        std::string prefix;
        std::streambuf* sbuf;
        string buffer;
    };


    class write_line_stream: public std::streambuf {
    public:
        write_line_stream(std::streambuf* s) : sbuf(s) {}
        ~write_line_stream() { overflow('\0'); }
    private:
        typedef std::basic_string<char_type> string;

        int_type overflow(int_type c) {

            if(traits_type::eq_int_type(traits_type::eof(), c))
                return traits_type::not_eof(c);
            switch(c) {
            case '\n':
            case '\r':
            {
                buffer += c;
                int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
                sbuf->sputn(&newline, 1);
                buffer.clear();

                return rc;
            }
            default:
                buffer += c;
                return c;
            }
        }

        std::streambuf* sbuf;
        string buffer;
    };

    static output_stream outputStream;
    static error_stream errorStream;
    static write_line_stream writeLineStream;

    public:
    static void Setup();

    static Writer Write;
    static Writer WriteLine;

    static Writer Err;
};
Run Code Online (Sandbox Code Playgroud)

控制台.cpp

#include "Console.h"

Console::Writer Console::Write(nullptr);
Console::Writer Console::WriteLine(nullptr);

Console::Writer Console::Err(nullptr);

Console::error_stream Console::errorStream(std::cout.rdbuf());
Console::write_line_stream Console::writeLineStream(std::cout.rdbuf());

void Console::Setup() {
    Write.rdbuf(std::cout.rdbuf());
    Err.rdbuf(&errorStream);
    WriteLine.rdbuf(&writeLineStream);
}
Run Code Online (Sandbox Code Playgroud)

主要.cpp

int main() {
    Console::Setup();
    Console::Write << "First" << "Second";
    Console::WriteLine << "Third";
    Console::WriteLine << "Fourth";
    Console::Write << "Fifth";
    Console::Error  << "Sixth";
    Console::ErrorLine  << "Seventh";
    Console::WriteLine << "Eighth";
}
Run Code Online (Sandbox Code Playgroud)

哪个应该给出输出

FirstSecondThird
Fourth
FifthERROR: SixthERROR: Seventh
Eighth
Press any key to continue...
Run Code Online (Sandbox Code Playgroud)

如有任何帮助和/或建议,我们将不胜感激。

Die*_*ühl 6

这里存在多个问题,确实需要不同的方法。有些描述似乎也不太清楚实际的愿望。最有问题的要求是显然需要在语句末尾插入换行符。这当然是可行的,但实际上确实需要暂时存在。

在讨论之前,我想指出,大多数其他语言提供的print-line(....)构造都使用函数调用来描述行上的内容。毫无疑问,换行符会去哪里。如果现在创建 C++ I/O,我非常确定它将基于可变参数(而不是vararg)函数模板。这种方式在表达式末尾打印一些东西是微不足道的。在生产线末端使用合适的操纵器(虽然可能不是std::endl,但可能是自定义的nl)将是一种简单的方法。

在表达式末尾添加换行符的基础知识是使用合适的临时对象的析构函数来添加它。直接的方法是这样的:

#include <iostream>

class newline_writer
    : public std::ostream {
    bool need_newline = true;
public:
    newline_writer(std::streambuf* sbuf)
        : std::ios(sbuf), std::ostream(sbuf) {
    }
    newline_writer(newline_writer&& other)
        : newline_writer(other.rdbuf()) {
        other.need_newline = false;
    }
    ~newline_writer() { this->need_newline && *this << '\n'; }
};

newline_writer writeline() {
    return newline_writer(std::cout.rdbuf());
}

int main() {
    writeline() << "hello, " << "world";
}
Run Code Online (Sandbox Code Playgroud)

这工作相当不错。不过,问题中的符号不​​使用函数调用。所以,而不是写

writeline() << "hello";
Run Code Online (Sandbox Code Playgroud)

看来有必要写

writeline << "hello";
Run Code Online (Sandbox Code Playgroud)

相反,仍然添加换行符。这使事情变得有点复杂:本质上,writeline现在需要是一个对象,它以某种方式导致另一个对象在使用时跳转到存在,以便后者可以在析构函数中完成其工作。使用转换将不起作用。但是,重载输出运算符以返回合适的对象确实有效,例如:

class writeliner {
    std::streambuf* sbuf;
public:
    writeliner(std::streambuf* sbuf): sbuf(sbuf) {}
    template <typename T>
    newline_writer operator<< (T&& value) {
        newline_writer rc(sbuf);
        rc << std::forward<T>(value);
        return rc;
    }
    newline_writer operator<< (std::ostream& (*manip)(std::ostream&)) {
        newline_writer rc(sbuf);
        rc << manip;
        return rc;
    }
} writeline(std::cout.rdbuf());

int main() {
    writeline << "hello" << "world";
    writeline << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

重载移位运算符的主要目的是创建合适的临时对象。他们不会试图弄乱字符流的内容。就我个人而言,我宁愿有额外的括号,也不愿使用这种有点混乱的方法,但它确实有效。重要的是操作符对于操纵器来说也是重载的,例如,允许第二个语句 with std::endlendl如果没有重载,就无法推断出 的类型。

下一步是写入前缀并混合多个流。这里重要的是要意识到您想要以下两件事之一:

  1. 立即将字符写入公共缓冲区。该缓冲区很像另一个流缓冲区,例如目标std::streambuf
  2. 如果字符应该在单独的流缓冲区中本地缓冲,则需要及时刷新相应的流,例如,在每次插入之后(通过设置位std::ios_base::unitbuf),或者最晚在表达式的末尾,例如,使用辅助类类似于newline_writer.

立即穿过角色是相当简单的。唯一稍微复杂的是知道何时写入前缀:在换行符或回车符之后的第一个非换行符、非回车符返回时(其他定义可能并且应该很容易适应)。重要的方面是流缓冲区并不真正缓冲,而是实际上将字符传递到底层[共享]流缓冲区:

class prefixbuf
    : public std::streambuf {
    std::string     prefix;
    bool            need_prefix = true;
    std::streambuf* sbuf;
    int overflow(int c) {
        if (c == std::char_traits<char>::eof()) {
            return std::char_traits<char>::not_eof(c);
        }
        switch (c) {
        case '\n':
        case '\r':
            need_prefix = true;
            break;
        default:
            if (need_prefix) {
                this->sbuf->sputn(this->prefix.c_str(), this->prefix.size());
                need_prefix = false;
            }
        }
        return this->sbuf->sputc(c);
    }
    int sync() {
        return this->sbuf->pubsync();
    }
public:
    prefixbuf(std::string prefix, std::streambuf* sbuf)
        : prefix(std::move(prefix)), sbuf(sbuf) {
    }
};
Run Code Online (Sandbox Code Playgroud)

剩下的工作就是在命名空间中设置相关的对象Console。然而,这样做相当简单:

namespace Console {
    prefixbuf    errorPrefix("ERROR", std::cout.rdbuf());
    std::ostream Write(std::cout.rdbuf());
    writeliner   WriteLine(std::cout.rdbuf());
    std::ostream Error(&errorPrefix);
    writeliner   ErrorLine(&errorPrefix);
}
Run Code Online (Sandbox Code Playgroud)

我除了添加换行符的方法创建了一个自定义类型之外,我认为它与原始类型相匹配。我认为无法避免临时对象在语句末尾自动创建换行符。

尽管如此,我认为您应该使用 C++ 习惯用法,而不是尝试在 C++ 中复制其他语言。在 C++ 中选择是否以换行符结束行的方法是通过合适的操纵器编写一个换行符,该换行符可能会出现在其中。