1

I tried my best on an informative title there, sorry if it's too long.

I'm working on an assignment for a CIS class on OOP using C++. For this class, we are usually required to implement a header that's provided without modifying it or #includeing any additional libraries. This current assignment is to add a void Read(std::ifstream&) and void Write(std::ofstream&) function to a Vector class that we implemented for the previous assignment.

The requirements are:

  • All files must be opened in binary mode.
  • The write function must write the internal array of type T to a file
  • The read function must read the file into an array of type T.

It's not specified what's to be done with the array that's read from the input file. My assumption would be that a reference would be returned, but the return type of read() is specified in the assignment as void. My best guess is that my professor is just interested in having us demonstrate the ability to read into an array, so that's what I'm shooting for. I've also considered concatenating the input file's array to the end of whichever vector object calls read(), but I don't think that's important so I digress.

I believe I've got write() in a solid place. This is what I'm working with at the moment: Write() is busted, I'll explain after code:

306 template <class T> void Vector<T>::Write (std::ofstream& ofile) const                                                                                                                                                                    
307 {                                                                                                                                                                                                                                    
308   if (!ofile.is_open()) ofile.open("vector.bin", std::ios::trunc | std::ios::binary);                                                                                                                                                    
309                                                                                                                                                                                                                                          
310   if (ofile.is_open())                                                                                                                                                                                                                   
311   {                                                                                                                                                                                                                                      
312     ofile.write((char*)&m_array, sizeof(m_array));                                                                                                                                                                                       
313   } else throw -6;                                                                                                                                                                                                                       
314                                                                                                                                                                                                                                          
315   ofile.close();                                                                                                                                                                                                                         
316 }     

When write() runs, it does so without error but it writes something pretty far from correct into the output file. Say m_array contains {2, 8, 16, 4}. The output file will contain:

01000000 00010000 00001000 00100000

when it should contain (assuming sizeof(int) is 4, which I know I shouldn't assume, but you get the idea):

01000000 00000000 00000000 00000000
00010000 00000000 00000000 00000000
00001000 00000000 00000000 00000000
00100000 00000000 00000000 00000000

It seems like when I cast m_array to a char*, my computer is being a smarty-pants and realizing that the char represented by the number 4 doesn't take 4 bytes to store.

I've got something that's sort of on the way to working that involves something like:

void* temp = m_array;
char* buffer = (char*)temp;

which seems to help the compiler forget that 4 bytes in m_array actually represent one element. It just feels so ugly, I feel like there has to be a logical way to do this or it wouldn't be an assignment in an introductory C++ class.

Anyway, the issue that I'm having is with the read function. If I'm correct, a vector object as they're implemented according to this assignement will only be able to read from a file that that particular object wrote to. If an algorithm written to read ints tries to read from a file that a Vector wrote to, it's just going to get garbage, right? That's one of the big challenges that I'm having, writing an algorithm that will be able to read from a file into an array of type T, regardless of the actual number of bytes required to express T.

The track I'm on right now is to do something like the following:

T temp;
char* buffer = new char[sizeof(T)];
// Loop the following for the entire file
ifile.read(buffer, sizeof(T));
temp = T(*buffer); // I know this won't work, this is what I'm trying to get at

If I'm understanding correctly, this will allow me to read the right amount of bytes into a char array. For example, if an arrray of ints were written to a file, it would read two bytes and store it in an array of chars two long. My buffer variable would then contain exactly the data I want.

How the heck do I get that data into temp!?!?

I'm going crazy here, especially because I know that atoi would work nicely, or at least merit some investigation. I don't think that would solve my problem of working for any of the primitive data types, but it'd at least be a start. I'm pulling my hair out because I've got an array of two bytes, if I put them together I'd have the int I want, but I just can't figure out how the heck to put them together. Am I going about this all the wrong way? Is this a ridiculous way to try to read from a binary file?

Can I just do something like temp = *(int*(buffer))? That seems pretty ugly to me, and I'd much rather understand the right way to do things than try every crazy idea until something compiles.

Thanks a lot for reading my word salad.

Community
  • 1
  • 1
Joseph Morgan
  • 210
  • 2
  • 8
  • 1
    1) Decide on a binary format for your data at the byte level. 2) Write code to convert to and from that format. 3) Use that code to read and write to and from the file. – David Schwartz Apr 28 '17 at 10:56
  • You don't suppose that the implementation that you have of that `Vector` class might be a tad pertinent to the question? – Mike Nakis Apr 28 '17 at 11:04
  • Also, this kind of serialization only works when entries are [POD`s](http://en.cppreference.com/w/cpp/concept/PODType) and when reading and writing are done in the same environment. – stefaanv Apr 28 '17 at 11:54
  • m_array being a pointer already, don't write its address `ofile.write((char*)m_array, sizeof(T) * m_length);` – Tezirg Apr 28 '17 at 14:24
  • Thanks David, that's a great solution and I think that's the direction I'm going to take. And Mike, the post was getting on the long side and I honestly didn't think it was particularly pertenant but I'd be happy to edit and post it. I've got a working version (prior to the addition of read() and write()) on github at https://www.github.com/josephemorgan91/Vector – Joseph Morgan Apr 29 '17 at 00:50

1 Answers1

1

I tend to use memcpy/memmove when dealing with binary data. To answer your question :

T temp;
char* buffer = new char[sizeof(T)];

// Loop the following for the entire file
ifile.read(buffer, sizeof(T));
//Put binary data into temp var
std::memcpy(&temp, buffer, sizeof(T));

For a more elegant solution, I would use a C structure that you can directly write / read from the file in one operation:

typedef struct vector_s {
    uint32_t   bytesPerElement;
    uint32_t   numberOfElements;
    char       data[1]; //Structure hack works because its the last element. 
}   vector_t;

And then play with the C structure hack : Is the "struct hack" technically undefined behavior?

Community
  • 1
  • 1
Tezirg
  • 1,629
  • 1
  • 10
  • 20
  • My current professor loves giving out zeros for solutions that work just fine, but aren't the way that he would do it. "Testing doesn't prove that it works," he loves to say. He also puts `using namespace std` in every header file he forces us to use, if that tells you anything about the guy and how I feel about him. – Joseph Morgan Apr 28 '17 at 13:29
  • Well, regarding the namespace do whatever you need to do. It doesn't change anything and we're not here to discuss style. – Tezirg Apr 28 '17 at 14:26