mustafa
mustafa

Reputation: 3905

Writing a struct into a stringstream in C++

I have some structs as:

struct dHeader
{
    uint8_t    blockID;
    uint32_t   blockLen;
    uint32_t   bodyNum;
};
struct dBody
{
    char       namestr[10];
    uint8_t    blk_version;
    uint32_t   reserved1;
}

and I have a stringstream as:

std::stringstream Buffer(std::iostream::in | std::iostream::out);

I want to write a dHdr and multiple dBody structs into Buffer with

Buffer << Hdr1;
Buffer << Body1;
Buffer << Body1;

I get the error:

error: no match for 'operator<<' in 'Buffer << Hdr1'

If I try it with:

Buffer.write(reinterpret_cast<char*>(&Hdr1), sizeof(dbHdr1));
Buffer.write(reinterpret_cast<char*>(&Body1), sizeof(Body1));
Buffer.write(reinterpret_cast<char*>(&Body2), sizeof(Body2));

I get confused about the packing and memory alignment.

Upvotes: 3

Views: 6359

Answers (2)

utnapistim
utnapistim

Reputation: 27365

For each of your structures, you need to define something similar to this:

struct dHeader
{
    uint8_t    blockID;
    uint32_t   blockLen;
    uint32_t   bodyNum;
};

std::ostream& operator<<(std::ostream& out, const dHeader& h)
{
     return out << h.blockID << " " << h.blockLen << " " << h.bodyNum;
}

std::istream& operator>>(std::istream& in, dHeader& h) // non-const h
{
    dHeader values; // use extra instance, for setting result transactionally
    bool read_ok = (in >> values.blockID >> values.blockLen >> values.bodyNum);

    if(read_ok /* todo: add here any validation of data in values */)
        h = std::move(values);
    /* note: this part is only necessary if you add extra validation above
    else
        in.setstate(std::ios_base::failbit); */
    return in;
}

(similar for the other structures).

Edit: An un-buffered read/write implementation has the following drawbacks:

  • it is unformatted; This may not be an issue for a small utility application, if you control where it is compiled and run, but normally, if you take the serialized data and run/compile the app on a different architecture you will have issues with endianness; you will also need to ensure the types you use are not-architecture dependent (i.e. keep using uintXX_t types).

  • it is brittle; The implementation depends on the structures only containing POD types. If you add a char* to your structure later, your code will compile the same, just expose undefined behavior.

  • it is obscure (clients of your code would expect to either see an interface defined for I/O or assume that your structures support no serialization). Normally, nobody thinks "maybe I can serialize, but using un-buffered I/O" - at least not when being the client of a custom struct or class implementation.

The issues can be ameliorated, by adding i/o stream operators, implemented in terms of un-buffered reads and writes.

Example code for the operators above:

std::ostream& operator<<(std::ostream& out, const dHeader& h)
{
     out.write(reinterpret_cast<char*>(&h), sizeof(dHeader));
     return out;
}

std::istream& operator>>(std::istream& in, dHeader& h) // non-const h
{
    dHeader values; // use extra instance, for setting result transactionally
    bool read_ok = in.read( reinterpret_cast<char*>(&values), sizeof(dHeader) );

    if(read_ok /* todo: add here any validation of data in values */)
        h = std::move(values);
    /* note: this part is only necessary if you add extra validation above
    else
        in.setstate(std::ios_base::failbit); */
    return in;
}

This centralizes the code behind an interface (i.e. if your class no longer supports un-buffered writes, you will have to change code in one place), and makes your intent obvious (implement serialization for your structure). It is still brittle, but less so.

Upvotes: 5

Kai Walz
Kai Walz

Reputation: 796

You can provide an overload for std::ostream::operator<< like

std::ostream& operator<<(std::ostream&, const dHeader&);
std::ostream& operator<<(std::ostream&, const dBody&);

For more information see this stackoverflow question.

Upvotes: 1

Related Questions