標準庫 iostream 處理文本流強大,但對二進制流支持極其簡陋。本文從核心需求出發,層層剝離次要矛盾,設計出一組精簡、正交的二進制流接口體系。
get_line(), get_word();二進制流需要 read(buffer, size), read_int32() 等精確到字節/位的控制。operator<</>> 依賴編譯期類型推導,無法表達「讀 3 字節無符號整數」這類需求。iostream)導致接口膨脹、語義混淆。二進制流兩個維度分類:
組合得 4 種,但核心需求只是「一組簡單的輸入流和輸出流」。於是砍掉:
// 基礎輸入流:只讀、只能順序訪問
class input_stream {
public:
virtual void read(void* buffer, uint32_t bytes) = 0;
virtual void skip(uint32_t bytes) = 0; // 跳過字節
virtual uint64_t tell() const = 0; // 當前位置
};
// 隨機訪問輸入流:繼承 input_stream,增加 seek/size
class random_istream : public input_stream {
public:
virtual void seek(int64_t offset, seek_origin origin) = 0;
virtual uint64_t size() const = 0;
};
// 輸出流對稱設計
class output_stream {
public:
virtual void write(void* buffer, uint32_t bytes) = 0;
virtual void flush() = 0;
virtual uint64_t tell() const = 0;
};
class random_ostream : public output_stream {
public:
virtual void seek(int64_t offset, seek_origin origin) = 0;
};
即在基類加 seek() + seekable()。缺點:
seekable() 返回值可能變化?),導致調用者需不斷 if (seekable()) seek()。seek/seekable(拋異常或返回 false)。random_istream IS-A input_stream(可隨機訪問的流當然可順序訪問)。不繼承流類,而是組合一個流指針:
class binary_reader {
input_stream* _stream;
public:
binary_reader(input_stream* s) : _stream(s) {}
uint8_t read_uint8();
uint16_t read_uint16_be(); // 大端序
uint32_t read_uint24_be(); // 3字節!
// ... LE 版本、有符號版本等
void skip(uint32_t n) { _stream->skip(n); }
uint64_t tell() const { return _stream->tell(); }
};
用法極其靈活:
input_stream* ist = ...; binary_reader r(ist); int v1 = r.read_uint8(); r.skip(4); int v2 = r.read_uint16_be(); // 若底層支持隨機訪問,可直接 seek random_istream* rist = ...; binary_reader r2(rist); rist->seek(4, seek_origin::current);
「解決問題第一步是簡化問題,抓住主要矛盾。越簡單的代碼越容易復用,越精心設計的代碼越難復用。」
binary_reader 中,流類只管字節搬運。此設計已在多媒體軟件實戰中驗證,兼顧了性能、擴展性與易用性。