std::具有二进制数据的字符串流流运算符

std::stringstream stream operators with binary data

本文关键字:字符串 运算符 二进制 std 数据      更新时间:2023-10-16

我正在为一个类编写一些测试用例,该类可能会从std::istream读取并写入std::ostream。作为测试过程的一部分,我想手动创建一个测试文件数据块,将其包装在std::stringstream中,然后将其传递给我的类进行处理。

我觉得我目前的解决方案缺乏,尽管它确实有效。我真的不喜欢将那些原始写入调用与reinterpret_cast一起使用。

std::stringstream file;
uint32_t version = 1;
uint32_t dataSize = 10;
uint32_t recordCount = 3;
file.write(reinterpret_cast<char*>(&version), sizeof(version));
file.write(reinterpret_cast<char*>(&dataSize), sizeof(dataSize));
file.write(reinterpret_cast<char*>(&recordCount), sizeof(recordCount));
myclass.read(file)

有没有办法使用流运算符以二进制形式写入此数据?我希望有更多类似于以下内容的内容。

std::stringstream file;
uint32_t version = 1;
uint32_t dataSize = 0;
uint32_t recordCount = 3;
file << version << dataSize << recordCount;
myclass.read(file);

如果我走这条路,提取一个数字会产生103这在 ascii 上下文中是预期的,但我显然试图避免以这种方式序列化我的数据。

您的代码存在问题:当您使用 reinterpret_cast,你实际上并不知道你在写什么到流,所以你不知道你在测试什么。 如果你想要测试您的代码如何对字节流做出反应二进制格式,您可以轻松初始化 std::istringstream具有任意字节流:

char bytes[] = { /*...*/ };
std::istringstream( std::string( std::begin( bytes ), std::end( bytes ) ) );

(如果您没有 C++11,您可以轻松编写自己的beginend .(

通过这种方式,您将确切地知道字节是什么,而不是取决于实现如何表示任何特定类型。

或者:如果您正在读取和写入二进制数据,则可能想要定义执行此操作的类,使用 >><< 。此类类与std::istreamstd::ostream,但逻辑上可以使用std::ios_base为常规错误报告和接口到std::streambuf 。 然后,该类将具有成员类似于以下内容:

namespace {
class ByteGetter
{
public:
    explicit            ByteGetter( ixdrstream& stream )
        :   mySentry( stream )
        ,   myStream( stream )
        ,   mySB( stream->rdbuf() )
        ,   myIsFirst( true )
    {
        if ( ! mySentry ) {
            mySB = NULL ;
        }
    }
    std::uint8_t        get()
    {
        int                 result = 0 ;
        if ( mySB != NULL ) {
            result = mySB->sgetc() ;
            if ( result == EOF ) {
                result = 0 ;
                myStream.setstate( myIsFirst
                    ?   std::ios::failbit | std::ios::eofbit
                    :   std::ios::failbit | std::ios::eofbit | std::ios::badbit ) ;
            }
        }
        myIsFirst = false ;
        return result ;
    }
private:
    ixdrstream::sentry  mySentry ;
    ixdrstream&         myStream ;
    std::streambuf*     mySB ;
    bool                myIsFirst ;
} ;
}
ixdrstream&
ixdrstream::operator>>( std::uint32_t&      dest )
{
    ByteGetter          source( *this ) ;
    std::uint32_t       tmp = source.get() << 24 ;
    tmp |= source.get() << 16 ;
    tmp |= source.get() <<  8 ;
    tmp |= source.get()       ;
    if ( *this ) {
        dest = tmp ;
    }
    return *this ;
}

(为了获得最大的可移植性,您可能不会避免 uint8_tuint32_t . 在此级别,编写代码时无需知道类型的确切大小有点困难,因此,如果您确定,您将永远不必移植到异国情调可能未定义的系统,可能值得保存你自己做了额外的工作。

您可以为 ostream 声明运算符<<(因此不仅是字符串流,还包括文件流(。 类似以下内容的内容可能有效(未经测试(,但您可能会遇到类型 (uint32_t( 的问题:

std::ostream& operator<<(std::ostream& stream, uint32_t value)
{
    stream.write(reinterpret_cast<char*>(&value), sizeof(value));
    return stream;
}
std::stringstream file;
file << version << dataSize << recordCount;

编辑:

由于类型值 has,因此已定义<<运算符。 一种替代方法是声明一个新的运算符<=

std::ostream& operator<=(std::ostream& stream, uint32_t value);
file <= version <= dataSize <= recordCount;

两个运算符都以从左到右的方式运行,因此这可能有效,但可能不是最好的解决方案。