0

I have a class called

Array<T>

You can create an array of any type.

template <typename T>
    class Array
    {
    private:
        T *m_array;
        int m_size;
        ...

For example,

Array <int> myArray(32) 

is an array of type int with a size of 32. This can store basic types or complex objects. For instance,

Array<Monster> monsters(32)

can hold an array of monster objects. Whatever type is used, I would like to save and load the array from disk.

One of these objects, say Actor, has a member variable (name) of type std::string. So, it is stored as

Array<Actor> actors(32)

I realized today that C's I/O functions know nothing about std::string, so loading std::string from file was causing a crash on shutdown. I want to upgrade my Save/Load functions to C++'s equivalent. My assumption is this will solve my problem with saving and loading objects that have a member variable of type std::string.

My original Save/Load functions: (Because they're in a header due to how templates work, I should mention they are more formally members of Array, or Array::save() and Array::load().)

            bool save(const string filename)
        { 
            FILE *fOut = NULL; 
            int written = 0;

            // Validate the array
            if (!isValidArray())
                return false;

            // Open the file
            fOut = fopen(filename.c_str(), "wb");
            if (fOut == NULL)
                return false; 

            // Write the array's size to file.
            fwrite(&m_size, sizeof(int), 1, fOut);

            // Write the array to file.
            written = fwrite(m_array, sizeof(T), m_size, fOut);
            fclose(fOut);

            // Validate if the array was written correctly
            if (written != m_size)
                return false; 

            return true;
        }

Load:

bool load(const string filename)
        {
            FILE *fIn = NULL;
            int read = 0;
            int size = 0;  

            // Open the file
            fopen_s(&fIn, filename.c_str(), "rb");
            if (fIn == NULL)
                return false;

            // Read the array's size from file.
            fread(&size, sizeof(int), 1, fIn);

            // Rleease the old array 
            release();

            // Initialize the new array
            if (!init(size))
                return false;

            // Read the array from file.
            read = fread(m_array, sizeof(T), size, fIn);
            fclose(fIn);

            // Validate if the array was written correctly.
            // If not, clean up the array object.
            if (read != size)
            {
                if (m_array != NULL)
                {
                    delete[] m_array;
                    m_array = NULL;
                    m_size = 0;
                }

                return false;
            } 

            return true;
        }

Overall, I would like to convert these to C++'s file handling.

This is my C++ attempt with save():

        bool save(const string filename)
        {  
            ofstream fOut; 

            // Validate the array
            if (!isValidArray())
                return false;

            // Open the file
            fOut.open(filename.c_str(), std::ios::binary | std::ios::out);
            if (!fOut.is_open())
                return false; 

            // Write the array's size to file.
            fOut << m_size;  

            // Write the array to file. ???? 
            fOut.write(m_array, m_size);

            fOut.close();

            return true;
        }

So, my problem is how do I save the array to file when its templated type could be a basic data type, struct, or class. My first assumption was this:

// Write the array to file. ???? 
fOut.write(m_array, m_size);

Any thoughts would be helpful. Thank you.

Finding out I need serialization, I overloaded operator<< for my Actor class, but would like further guidance on how to use it for this purpose. Actor has a std::string that I need to save to file.

std::ofstream & X2D::operator<<(std::ofstream & output, const Actor & p)
{ 
    // insert magical code here 
    return output;  
}
Phil
  • 607
  • 1
  • 8
  • 22
  • 3
    What you're looking for is called serialization. – Retired Ninja May 29 '17 at 22:58
  • _"Because they're in a header due to how templates work"_ As an aside, you should consider moving those definitions out of the class definition for clarity and nicer code. They can still be in a header. – Lightness Races in Orbit May 29 '17 at 23:38
  • _"I have a class called [..] You can create an array of any type."_ What's wrong with `std::array`? – Lightness Races in Orbit May 29 '17 at 23:38
  • 1
    This question amounts to "how do I serialise data of arbitrary types in C++?" which, despite being an extremely broad question (did you research any options?), may basically be answered "you don't". – Lightness Races in Orbit May 29 '17 at 23:39
  • The only types you can arbitrarily serialize are POD types. You can use templates to deal with that case. For non-POD types, I would assume that they provide the appropriate `operator<<` for serialization. Since your container provides a save operation, you could document your container as requiring its elements to define a proper `operator<<` for serialization. – user2296177 May 29 '17 at 23:54
  • @user2296177, I would like to learn how to use operator<<. Coming to think of it, I've seen some code using that in the past for objects. – Phil May 30 '17 at 00:02
  • [There is a short, but good, write up on writing your own `<<` and `>>` operators here.](https://stackoverflow.com/questions/4421706/operator-overloading) In the guts of the function just use the existing `<<` and `>>` for all of the members of the class with a space to separate one member from the next. If you have spaces in a member, for example `MonsterName = "Big Freaking Dragon!!!", you have to get a little bit smarter and use `std::getline` and something other than space to separate the members. – user4581301 May 31 '17 at 18:47
  • @user4581301, I got it to work with overloading << and >>. Thanks for the help! Learning something new. Using these allow my classes to be flexible while saving. – Phil Jun 05 '17 at 00:04

2 Answers2

0

Consider making your save function a member of Array and overloading it as needed. You might find std::enable_if feature from C++11 very useful to only allow the serialization for the types your code is ready for. Your

 fOut.write(m_array, m_size);

will not work from scratch, you'll need to implement it yourself. As of the time of writing, C++ still is not Java in this regard.

iehrlich
  • 3,572
  • 4
  • 34
  • 43
  • save() and load() are member functions of Array. I added a note above. – Phil May 29 '17 at 23:05
  • Like I wrote, same as in C, it's mostly your (or merely your classes) concern to manage the serialization of the data. If you use templatization for a limited amount of types, specialize the save/load functions for these types manually. If you're trying to write a generic container that serializes its members, then you've got trouble. The concept of `traits` might be useful - you might craft your container in such way that instantiation of `Array` will require `T` to have proper serialization interfaces exposed. In the latter case, you'll just call such methods in save/load of your Array. – iehrlich May 29 '17 at 23:11
  • @Phil the easy out is the for loop you have at the bottom of your question and writing the appropriate `>>` and `<<` operators for all of your custom objects you wish to store. Text is gross and inefficient, but it is stupidly easy to work with. – user4581301 May 30 '17 at 00:02
0

This ended up being my solution. Overloading operators << and >> helped my classes remain flexible while saving.

I'll post the code below in case it's useful to anybody. The string saving looks somewhat of a mess, and could possibly be improved, but it successfully saves/loads the length and then saves/loads an std::string.

std::ostream& operator<<(std::ostream& os, const Monster& obj)
{  
int length = obj.m_name.length() + 1;  
char buffer[1080];

strcpy_s(buffer, 1080, obj.m_name.c_str());

os << length;
os.write(buffer, strlen(buffer) + 1);
os << endl;

os << obj.m_x << endl;
os << obj.m_y << endl;
os << obj.m_hp << endl;

return os;
 }

 std::istream& operator>>(std::istream& is, Monster& obj)
 { 
int length;
char buffer[1080];  

is >> length;
is.readsome(buffer, length);

std::string sBuffer(buffer, length); 
obj.m_name = sBuffer;

is >> obj.m_x;
is >> obj.m_y;
is >> obj.m_hp;

return is;
 }
Phil
  • 607
  • 1
  • 8
  • 22