BasicOstream

前置知识

流式一个基础并且核心的概念,是c++用来抽象数据输入和输出操作的概念,可以看做是一个数据传送带或者水管,数据可以从源头输入数据,叫做输入流,可以从程序流出到目的地,叫做输出流。

它屏蔽了底层硬件设备(键盘、显示器、硬盘、网卡)的差异,让程序员可以用统一的方式(如 << 和 >> 操作符)来读写数据,而不用关心数据到底去了哪里。

// 对程序员来说,写法几乎一样:
std::cout << "Hello";   // 流向控制台
fileStream << "Hello";  // 流向文件
socketStream << "Hello"; // 流向网络

我们深入分析的 basic_ostream 就是 C++ 标准库流体系的核心基类。整个体系可以看作一个继承树:

层次类名作用
核心基类ios_base管理流状态、格式标志(如进制、宽度)
中间基类basic_ios管理流缓冲区指针 rdbuf(),处理错误状态
输入流basic_istream负责读数据(>> 操作符)
输出流basic_ostream负责写数据(<< 操作符)
输入输出流basic_iostream同时支持读写(继承自上述两者)
根据数据的目的地来看,可以将流分为几个大类,底层机制是一样的,区别在于内部绑定的 “流缓冲区(streambuf)” 不同。

数据流入的是磁盘文件的话,就叫做文件流。 类名:ifstream(读文件)、ofstream(写文件)、fstream(读写文件) 关联:它内部绑定了一个 filebuf(文件缓冲区),负责将数据真正写入硬盘。

#include <fstream>
std::ofstream file("test.txt");
file << "写入文件";   // 用法和 cout 完全一样!
file.close();

字符串流是将内存中的字符串上进行读写,用于数据类型转换或者作为内存中的临时存储,避免频繁的IO操作,内部绑定了stringbuf字符串缓冲区。

#include <sstream>
std::ostringstream oss;
oss << "年龄: " << 25;
std::string result = oss.str(); // 把数据“流”进字符串里

标准流是操作系统默认帮你打开的流,用于连接程序和终端:

  • std::cout(标准输出流):通常指向控制台屏幕。
  • std::cin(标准输入流):通常指向键盘。
  • std::cerr(标准错误流):无缓冲,立即输出错误信息。
class basic_ostream : virtual public basic_ios<_Elem, _Traits> { // control insertions into a stream buffer
public:
    using _Myios = basic_ios<_Elem, _Traits>;
    using _Mysb  = basic_streambuf<_Elem, _Traits>;
    using _Iter  = ostreambuf_iterator<_Elem, _Traits>;
    using _Nput  = num_put<_Elem, _Iter>;
    explicit __CLR_OR_THIS_CALL basic_ostream(basic_streambuf<_Elem, _Traits>* _Strbuf, bool _Isstd = false) {
        _Myios::init(_Strbuf, _Isstd);
    }
    __CLR_OR_THIS_CALL basic_ostream(_Uninitialized, bool _Addit = true) {
        if (_Addit) {
            this->_Addstd(this); // suppress for basic_iostream
        }
    }
protected:
    __CLR_OR_THIS_CALL basic_ostream(basic_ostream&& _Right) noexcept(false) {
       _Myios::init();
        _Myios::move(_STD move(_Right));
    } 
    basic_ostream& __CLR_OR_THIS_CALL operator=(basic_ostream&& _Right) noexcept /* strengthened */ {
        this->swap(_Right);
        return *this;
    }
    void __CLR_OR_THIS_CALL swap(basic_ostream& _Right) noexcept /* strengthened */ {
        if (this != _STD addressof(_Right)) {
            _Myios::swap(_Right);
        }
    }
public:
    __CLR_OR_THIS_CALL basic_ostream(const basic_ostream&)            = delete;
    basic_ostream& __CLR_OR_THIS_CALL operator=(const basic_ostream&) = delete;
    __CLR_OR_THIS_CALL ~basic_ostream() noexcept override {}
    using int_type = typename _Traits::int_type;
    using pos_type = typename _Traits::pos_type;
    using off_type = typename _Traits::off_type;
    class _Sentry_base { // stores thread lock and reference to output stream
    public:
        __CLR_OR_THIS_CALL _Sentry_base(basic_ostream& _Ostr) : _Myostr(_Ostr) { // lock the stream buffer, if there
            const auto _Rdbuf = _Myostr.rdbuf();
            if (_Rdbuf) {
                _Rdbuf->_Lock();
            }
        }
        __CLR_OR_THIS_CALL ~_Sentry_base() noexcept { // destroy after unlocking
            const auto _Rdbuf = _Myostr.rdbuf();
            if (_Rdbuf) {
                _Rdbuf->_Unlock();
            }
        }
        basic_ostream& _Myostr; // the output stream, for _Unlock call at destruction
        _Sentry_base& operator=(const _Sentry_base&) = delete;
    };
   class sentry : public _Sentry_base {
    public:
        explicit __CLR_OR_THIS_CALL sentry(basic_ostream& _Ostr) : _Sentry_base(_Ostr) {
            if (!_Ostr.good()) {
                _Ok = false;
                return;
            }
           const auto _Tied = _Ostr.tie();
            if (!_Tied || _Tied == _STD addressof(_Ostr)) {
                _Ok = true;
                return;
            }
            _Tied->flush();
            _Ok = _Ostr.good(); // store test only after flushing tie
        }
        _STL_DISABLE_DEPRECATED_WARNING
        __CLR_OR_THIS_CALL ~sentry() noexcept {
#if !_HAS_EXCEPTIONS
            const bool _Zero_uncaught_exceptions = true;
#elif _HAS_DEPRECATED_UNCAUGHT_EXCEPTION
            const bool _Zero_uncaught_exceptions = !_STD uncaught_exception(); // TRANSITION, ArchivedOS-12000909
#else // ^^^ _HAS_DEPRECATED_UNCAUGHT_EXCEPTION / !_HAS_DEPRECATED_UNCAUGHT_EXCEPTION vvv
            const bool _Zero_uncaught_exceptions = _STD uncaught_exceptions() == 0;
#endif // ^^^ !_HAS_DEPRECATED_UNCAUGHT_EXCEPTION ^^^  
            if (_Zero_uncaught_exceptions) {
                this->_Myostr._Osfx();
            }
        }
        _STL_RESTORE_DEPRECATED_WARNING 
        explicit __CLR_OR_THIS_CALL operator bool() const {
            return _Ok;
        }
        __CLR_OR_THIS_CALL sentry(const sentry&)            = delete;
        sentry& __CLR_OR_THIS_CALL operator=(const sentry&) = delete; 
    private:
        bool _Ok; // true if stream state okay at construction
    };

虚继承自basic_ios,basic_iostream 同时继承 basic_istream 和 basic_ostream,而两者都继承自 basic_ios。

            basic_ios
          /          \
   basic_istream   basic_ostream   ← 这里是虚继承
          \          /
           basic_iostream

类型别名:

using _Myios = basic_ios<_Elem, _Traits>;   // 基类
using _Mysb  = basic_streambuf<_Elem, _Traits>;  // 流缓冲区
using _Iter  = ostreambuf_iterator<_Elem, _Traits>;  // 输出迭代器
using _Nput  = num_put<_Elem, _Iter>;  // 数值格式化器

这里实现不同类型的构造:

绑定流缓冲区,将输出流绑定到一个具体的流缓冲区(文件、字符串、控制台等)

explicit basic_ostream(basic_streambuf<_Elem, _Traits>* _Strbuf, bool _Isstd = false) {
    _Myios::init(_Strbuf, _Isstd);
}

特殊构造,未初始化状态, 用于 basic_iostream 这样的派生类,它们需要先构造,再初始化基类, _Uninitialized 是一个空标记类型,表示"不初始化流缓冲区"。

basic_ostream(_Uninitialized, bool _Addit = true) {
    if (_Addit) {
        this->_Addstd(this);  // 注册到标准流列表
    }
}

移动构造,支持移动语义,避免不必要拷贝,移动可能需要抛出异常,这个取决流缓冲区:

basic_ostream(basic_ostream&& _Right) noexcept(false) {
    _Myios::init();           // 先初始化基类(无缓冲区)
    _Myios::move(_STD move(_Right));  // 移动基类资源
}

由于流对象不可拷贝,这是设计上的选择,std::cout不能拷贝,因此我们需要删除拷贝操作

basic_ostream(const basic_ostream&) = delete;
basic_ostream& operator=(const basic_ostream&) = delete;

内部有一个叫做Sentry的对象,是流操作的防护,每次执行<<操作的时候需要检查流状态是否good,刷新tie,如果有绑定的输入流,先刷新,加锁在多线程的环境下保护流缓冲区,还可以解析的时候解锁刷新确保数据输出。

class _Sentry_base {  // 基类:负责加锁/解锁
public:
    _Sentry_base(basic_ostream& _Ostr) : _Myostr(_Ostr) {
        const auto _Rdbuf = _Myostr.rdbuf();
        if (_Rdbuf) {
            _Rdbuf->_Lock();  // ← 加锁!
        }
    }
    
    ~_Sentry_base() noexcept {
        const auto _Rdbuf = _Myostr.rdbuf();
        if (_Rdbuf) {
            _Rdbuf->_Unlock();  // ← 解锁!
        }
    }
    
    basic_ostream& _Myostr;
};

class sentry : public _Sentry_base {
public:
    explicit sentry(basic_ostream& _Ostr) : _Sentry_base(_Ostr) {
        // 1. 检查流状态
        if (!_Ostr.good()) {
            _Ok = false;
            return;
        }
        
        // 2. 刷新绑定的输入流(tie)
        const auto _Tied = _Ostr.tie();
        if (!_Tied || _Tied == &_Ostr) {
            _Ok = true;
            return;
        }
        
        _Tied->flush();  // 刷新绑定的流
        _Ok = _Ostr.good();
    }
    
    ~sentry() noexcept {
        // 析构时调用 _Osfx()(输出刷新和清理)
        if (_Zero_uncaught_exceptions) {
            this->_Myostr._Osfx();
        }
    }
    
    explicit operator bool() const { return _Ok; }  // 检查是否成功
    
private:
    bool _Ok;
};

模板构造

#include <fmt/format.h>

template<typename CharT, typename Traits>
std::basic_ostream<CharT, Traits>& operator<<(
    std::basic_ostream<CharT, Traits>& os,
    const Person& p
) {
    os << "Person{name: " << p.name 
       << ", age: " << p.age 
       << ", hobbies: ["
       << fmt::join(p.hobbies, ", ")
       << "]}";
    return os;
}

这里是个利用函数模板和fmt库实现的自定义数据结构的流输出操作。CharT 和 Traits 是 C++ 标准库流设计的核心,理解了它们,你就理解了 basic_ostream 为什么能同时支持 char、wchar_t 甚至自定义字符类型。


CharT是字符类型,决定了流中每个字符用什么类型去存储:

实例化CharT说明
std::ostreamchar单字节 ASCII/UTF-8
std::wostreamwchar_t宽字符(Windows 上 2 字节,Linux 上 4 字节)
std::u8ostream (C++20)char8_tUTF-8 字符
std::u16ostream (C++20)char16_tUTF-16 字符
std::u32ostream (C++20)char32_tUTF-32 字符
// 1. char 版本
std::ostream& os = std::cout;      // CharT = char
os << "Hello";                     // 存储 char 序列

// 2. wchar_t 版本
std::wostream& wos = std::wcout;   // CharT = wchar_t
wos << L"Hello";                   // 存储 wchar_t 序列

// 3. 模板函数自动适配
template<typename CharT, typename Traits>
void print(std::basic_ostream<CharT, Traits>& os, const CharT* str) {
    os << str;  // 自动适配 char 或 wchar_t
}

print(std::cout, "Hello");     // CharT = char
print(std::wcout, L"Hello");   // CharT = wchar_t

Traits是策略类,用于定义字符类型CharT的各种操作和行为,比如说比较两个字符,计算字符串长度,EOF值是多少,怎么复制字符数组。