扩展std :: cout

时间:2018-01-30 21:01:58

标签: c++ winapi cout ostream streambuf

我想扩展<script type="text/javascript"> var val = 0; jQuery(document).ready(function ($) { $('.btn-minuse').on('click', function () { val = parseInt($(this).parent().siblings('input').val()); if(val > 0){ $(this).parent().siblings('input').val(val - 1)); } }); $('.btn-pluss').on('click', function () { $(this).parent().siblings('input').val(parseInt($(this).parent().siblings('input').val()) + 1) }); }); </script> 的用法以使用我自己的控制台/ cout包装类。

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

std::cout

打印std::ostream Write; Write << "Hello, I am " << 99 << " years old.";

Hello, I am 99 years old.

打印std::ostream WriteLine; WriteLine << "Hello, I am " << 99 << " years old."; (一个实际的新行,而不仅仅是它被转义)

我希望扩展它以使错误流(例如Hello, I am 99 years old.\nError)在消息之前加前缀ErrorLine并以不同的颜色打印。

我知道我必须创建自己的流来添加此功能,然后我跟着C++ cout with prefix前缀"ERROR: ",这几乎是我想要但不完全的。我无法弄清楚如何在流的末尾添加一个新行,并且前缀不可靠,特别是当我不止一个打印语句时。

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

什么行不通

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

我可以让它用于单个语句,但是一旦我添加了另一个打印语句,我尝试打印的内容将切换顺序,后期/预修复不会添加到正确的位置,或者我最终会垃圾。

我不关心wchars,因为对于单个char我们总是只需要一个字节。此外,我们只会在Windows 10上工作。

这是我到目前为止所做的:

Console.h

First\nSecond\n

Console.cpp

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

Main.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);
}

哪个应该输出

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

感谢任何帮助和/或建议。

1 个答案:

答案 0 :(得分:2)

这里有许多问题需要不同的方法。一些描述似乎也没有明确的实际愿望。最有问题的要求是需要在声明结尾处插入换行符。这当然可行,但实际上需要暂时存在。

在去那里之前,我想指出大多数提供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";
}

这很合理。但问题中的符号并没有使用函数调用。所以,而不是写

writeline() << "hello";

似乎有必要写

writeline << "hello";

而是仍然添加换行符。这使问题变得复杂:基本上,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;
}

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

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

  1. 立即将字符写入公共缓冲区。缓冲区最像是另一个流缓冲区,例如目的地std::streambuf
  2. 如果字符应该在单独的流缓冲区中本地缓冲,则需要及时刷新相应的流,例如,在每次插入之后(通过设置std::ios_base::unitbuf位)或最后,在结束时表达式,例如,使用类似于newline_writer的辅助类。
  3. 立即传递角色非常简单。唯一轻微的复杂因素是知道何时写入前缀:在第一个非换行符后,在换行符或回车符后返回非回车符(其他定义可能且应该很容易适应)。重要的一点是流缓冲区并不真正缓冲,但实际上将字符传递给底层[共享]流缓冲区:

    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) {
        }
    };
    

    剩下的业务是在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);
    }
    

    我除了添加换行符的方法创建了一个自定义类型,我认为它与原始类型匹配。我不认为可以避免临时对象在语句结束时自动创建换行符。

    所有这一切,我认为你应该使用C ++习语,尝试在C ++中复制其他语言。在C ++中选择行是否以换行符结尾的方式是编写一个换行符,其中一行应该通过合适的操作符出现。