Reading/writing files to/from a struct/class

2019-03-02 15:21发布

问题:

I'd like to read a file into a struct or class, but after some reading i've gathered that its not a good idea to do something like:

int MyClass::loadFile( const char *filePath ) {

            ifstream file ( filePath, ios::in | ios::binary );

            file.read ((char*)this, 18);

            file.close();

            return 0;

        }

I'm guessing if i want to write a file from a struct/class this isn't kosher either:

void MyClass::writeFile( string fileName ) {

        ofstream file( fileName, ofstream::binary ); 

        file.write((char*)this, 18);

        file.close();

    }

It sounds like the reason i don't want to do this is because even if the data members of my struct add up to 18 bytes, some of them may be padded with extra bytes in memory. Is there a more correct/elegant way to get a file into a class/struct like this?

回答1:

The preferred general technique is called serialization.

It is less brittle than a binary representation. But it has the overhead of needing to be interpreted. The standard types work well with serialization and you are encouraged to make your class serialize so that a class containing your class can easily be serialized.

class MyClass {
     int x;
     float y;
     double z;
     friend std::ostream& operator<<(std::ostream& s, MyClass const& data);
     friend std::istream& operator>>(std::istream& s, MyClass& data);
};

std::ostream& operator<<(std::ostream& s, MyClass const& data)
{
    // Something like this
    // Be careful with strings (the input>> and output << are not symmetric unlike other types)
    return str << data.x << " " << data.y << " " << data.z << " ";
}

// The read should be able to read the version printed using <<
std::istream& operator>>(std::istream& s, MyClass& data)
{
    // Something like this
    // Be careful with strings.
    return str >> data.x >> data.y >> data.z;
}

Usage:

int main()
{
    MyClass   plop;
    std::cout << plop;  // write to a file
    std::cin  >> plop;  // read from a file.


    std::vector<MyClass>  data;

    // Read a file with multiple objects into a vector.
    std::ifstream  loadFrom("plop");
    std::copy(std::istream_iterator<MyClass>(loadFrom), std::istream_iterator<MyClass>(),
              std::back_inserter(data)
             );


    // Write a vector of objects to a file.
    std::ofstream   saveTo("Plip");
    std::copy(data.begin(), data.end(), std::ostream_iterator<MyClass>(saveTo));

    // Note: The stream iterators (std::istream_iterator) and (std::ostream_iterator)
    //       are templatized on your type. They use the stream operators (operator>>)
    //       and (operator<<) to read from the stream.
}


回答2:

The answer is : there is no silver bullet to this problem.

One way you can eliminate the padding to ensure that the data members in your class is to use(in MSVC which you are using)

#pragma pack( push, 1 )

class YourClass {
    // your data members here
    int Data1;
    char Data2;
    // etc...
};

#pragma pack( pop )

The main usefulness of this approach is if your class matches a predefined format such as a bitmap header. If it is a general purpose class to represent a cat, dog, whatever then dont use this approach. Other thing if doing this is to make sure you know the length in bytes of the data types for your compiler, if your code is EVER going to be multi platform then you should use explicit sizes for the members such as __int32 etc.

If this is a general class, then in your save member, each value should be written explicitly. A tip to do this is to create or get from sourceforge or somewhere good code to help do this. Ideally, some code that allows the member to be named, I use something similar to :

SET_WRITE_DOUBLE( L"NameOfThing", DoubleMemberOfClass );
SET_WRITE_INT( L"NameOfThing2", IntMemberOfClass );
// and so on...

I created the code behind these macros, which I am not sharing for now but a clever person can create their own code to save named to stream in an unordered-set. This I have found is the perfect approach because if you add or subtract data members to your class, the save/load is not dependent on the binary representation and order of your save, as your class will doubtless evolve through time if you save sequentially this is a problem you will face.

I hope this helps.