Saving in binary format example here!

Started by
5 comments, last by jflanglois 18 years, 11 months ago
I have been wondering for a long time how you would save a class in binary format if you don't declare everything on the stack. If you try to do this following the procedure shown in most tutorials and books on the subject, it will just save 4 bytes of data representing a pointer to the location of the object in memory. I finally figured out how to save classes in binary format, even when the use pointers or, as in the example, std::strings or something similar with variable-length containers. I am going to illustrate it here, because I have _never_ been able to find this anywhere on the web. :( Here it goes, along with an explanation, following here: The class we are going to save is called Item, a simple class storing a "resource ID", a unique ID string for the item, like "ITEM_WAND_01", a "display name", which would be displayed in-game as the item's name, like "spoon" or "wand of Merlin", a description, like "A powerful staff to conduct magical energies", or "For eating". Also, it has a weight, in pounds. The Item class is a bare-bones example of something that might be found in an RPG or some other type of game. We are going to save it in binary format, then load it again and read out the contents. Here we go! (Its a bit rough, I took it straight from one of my projects, and it hasn't been cleaned up). Source from the item class: item.h - the class definition

#ifndef ITEM_CLASS
#define ITEM_CLASS

#include <string>

#include "stringops.h"

class Item
{
    public:
    Item();
    ~Item();
    
    //Set member variables
    void operator () ( std::string, std::string, 
                       std::string, unsigned int );
    
    //Set member variables
    void operator () ( const char*, const char*, const char*, unsigned int );
    
    //Get member variables 
    std::string & GetResourceName();
    std::string & GetDisplayName();
    std::string & GetDescription();
    unsigned int & GetWeight();
    
    
    private:
    std::string resource_name;
    std::string display_name;
    std::string description;
    unsigned int weight;
};


#endif


item.cpp - the class methods

#ifndef ITEM_CPP
#define ITEM_CPP

#include "item.h"


Item::Item()
: resource_name("UNDEFINED")
{}

Item::~Item()
{}

void Item::operator () ( const char* new_resource_name, 
                         const char* new_display_name, 
                         const char* new_description, 
                         unsigned int new_weight)
{
    (*this)(std::string(new_resource_name),
            std::string(new_display_name),
            std::string(new_description),
            new_weight );
}
    
void Item::operator () ( std::string new_resource_name, 
                         std::string new_display_name, 
                         std::string new_description, 
                         unsigned int new_weight)
{
    resource_name = Left( new_resource_name, 25 );
    display_name  = Left( new_display_name, 25 );
    description = Left( new_description, 200 );
    weight = new_weight;
}
    
std::string & Item::GetResourceName()
{
    return resource_name;
}

std::string & Item::GetDisplayName()
{
    return display_name;
}

std::string & Item::GetDescription()
{
    return description;
}

unsigned int & Item::GetWeight()
{
    return weight;
}

#endif


Now for the functions which read and write the item to a binary file. There are numerous ways in which it could be improved, and it works only for items, but you could easily modify it for your own classes (provided you understand what I'm doing) or make it more general. Here is the source for the read and write item functions - WriteItem will write the item to a binary file with filename [item.resource_name.RIF]. ReadItem will read from the file specified by std::string "filename" into "item".

#include <fstream>
#include <string>

#include "item.h"

bool WriteItem( Item & item )
{
    //this is the filename we are writing to - [item.resource_name].RIF
    std::string filename = item.GetResourceName() + ".RIF";

    //open a stream to the file, which will create or copy over the filename you
    //specified.  Open it in binary format, as "std::ofstream::binary".

    std::ofstream fout ( filename.c_str(), std::ofstream::binary );

    //Get the size, in bytes, of the actual char* part of "item"'s resource_name.
    //Add 1 to it to account for the '\0' character that postpends a char*.
    //The size is equal to 1 byte per character, as a char variable is equal to 1 byte.
    //If you aren't saving a char, which is shown when we write out the weight, you
    //would use this: "sizeof( [variable_type or class_name] )".
    //sizeof( char ) returns 1, sizeof( int ) returns 4, etc.  You can use this on
    //classes you design, as long as they don't contain any variables on the heap.
    long size = item.GetResourceName().length() + 1;
    
    //Write out the size of the string you are writing out, so you can load it
    //properly later.
    
    fout.write( (char*)&size, sizeof(&size) );

    //Now, take out a number of bytes equal to the length of resource_name + 1, as we got
    //above.  Write out the actual character array to the file, as shown below.
    
    fout.write( (item.GetResourceName().c_str()), size );

    ////////////////////////////////////////////////////

    //Repeat above, with item.display_name.

    size = item.GetDisplayName().length() + 1;
    
    fout.write( (char*)&size, sizeof(&size) );
    
    fout.write( (item.GetDisplayName().c_str()), size );

    ////////////////////////////////////////////////////

    //Repeat as above, but with item.description
    
    size = item.GetDescription().length() + 1;
    
    fout.write( (char*)&size, sizeof(&size) );
    
    fout.write( (item.GetDescription().c_str()), size );

    ////////////////////////////////////////////////////

    //Write out item.weight.  You cast it to (char*), because this effectively tells it
    //to write it out byte by byte.  You would do this with anything you use this
    //statement on, except for actual (char*)s, as above.
    
    fout.write( (char*)&item.GetWeight(), sizeof(unsigned int) );
    
    //Close the file - we have saved all information from the class!

    fout.close();
    return true;
}

bool ReadItem( std::string filename, Item & item )
{
    std::ifstream fin ( filename.c_str(), std::ofstream::binary );
    
    //this will store the size, in bytes, of each variable to read into.
    long size;
    
    //these store the actual variables to read into.
    char * resource_name = 0;
    char * display_name = 0;
    char * description = 0;
    unsigned int weight = 0;
    
    //read in the size of the resource name
    fin.read( (char*)&size, sizeof( &size ) );
    
    //size resource_name appropriately
    resource_name = new char[size];
    
    //read in resource_name, a number of bytes equal to what we wrote out the size was.
    fin.read( resource_name, size );

    ///////////////////////////////////////////    

    //as above, but for the display name
    fin.read( (char*)&size, sizeof( &size ) );
    
    display_name = new char[size];
    
    fin.read( display_name, size );

    ///////////////////////////////////////////
    
    //as above, but for the description

    fin.read( (char*)&size, sizeof( &size ) );
    
    description = new char[size];
    
    fin.read( description, size );

    ///////////////////////////////////////////
    
    //read directly into "weight"

    fin.read( (char*)&weight, sizeof(&weight ) );

    //we have read all data!  store it in "item".
    
    item( resource_name, display_name, description, weight );
    
    //release memory taken up by char arrays
    delete [] resource_name;
    delete [] display_name;
    delete [] description;

    //Close the file - we have loaded in all the member variables.  "item" now stores
    //an exact copy of the Item that was saved. 
    fin.close();

    return true;
}



The source of the functions should be commented sufficiently. If you have questions or comments, please post below! Hope it was helpful to at least one person, besides myself. [edit]Fixed!
my siteGenius is 1% inspiration and 99% perspiration
Advertisement
cool. Though I don't believe int is guaranteed to be a specific size on all machines like char or long is (at least I think long is), so it will fail when reading files on other machines if it uses different size int, also if it is different Endian I think it will fail, since you don't enforce the writing/reading one way or another.
Why do you have header guards in a .cpp file?
Quote:Original post by load_bitmap_file
Why do you have header guards in a .cpp file?


Because the more preprocessor directives in your code the more professional it looks.
Chess is played by three people. Two people play the game; the third provides moral support for the pawns. The object of the game is to kill your opponent by flinging captured pieces at his head. Since the only piece that can be killed is a pawn, the two armies agree to meet in a pawn-infested area (or even a pawn shop) and kill as many pawns as possible in the crossfire. If the game goes on for an hour, one player may legally attempt to gouge out the other player's eyes with his King.
Examples may be hard to come by, but I reference this page about the theory on the forums all the time... :S
Quote:Original post by smart_idiot
Quote:Original post by load_bitmap_file
Why do you have header guards in a .cpp file?


Because the more preprocessor directives in your code the more professional it looks.


yes! (Actually, I thought you needed them :). I am still not positive of where you use them and not, but I'm pretty sure I'm doing it right (with this new advice).


I just cooked this up as a rough example, but for the task at hand, on my machine, it worked. I'm now in the process of sprucing it up. Thanks for looking at it.
my siteGenius is 1% inspiration and 99% perspiration
</sarcasm>


jfl.

This topic is closed to new replies.

Advertisement