1

I want to dump some derived class into file, then dump it back.

here the the class definition:

// base.h
#include <string>
class Base {
 public:
  virtual std::string show_string() = 0;
  int b;
};

class A : public Base {
 public:
  std::string show_string() override { return "this is A"; }
  int a;
};

the dumper code is:

#include <bits/stdc++.h>
#include "./base.h"
using namespace std;

int main() {
  std::fstream f;
  f.open("a.bin", std::ios::out);
  for (int i = 0; i < 10; ++i) {
    A a;
    a.a = i;
    a.b = i - 1;
    f.write((char*)&a, sizeof(a));
  }
  f.close();
}

the loader code is:

#include <bits/stdc++.h>
#include "./base.h"
using namespace std;

int main() {
  std::fstream f;
  f.open("a.bin", std::ios::in);
  char buff[65536];
  f.write(buff, sizeof(buff));
  A* a = (A*)buff;
  cout << a->show_string() << endl;
  f.close();
}

the loader crashed, I think the problem happened because there is a virtual pointer in A, which lost the address when it is loaded back.

so, how can i make it correct?

nothingisme
  • 119
  • 5
  • you cannot actually write (load) class with virtual function that way. (write may work, but it's not much useful anyway.) – apple apple Nov 20 '22 at 18:19
  • `f.write((char*)&a, sizeof(a))` only works if `a` is a POD type, you can't do that with a virtual class. You need to write a proper serialiser – Alan Birtles Nov 20 '22 at 18:19
  • 3
    `f.write((char*)&a, sizeof(a));` -- `A* a = (A*)buff;` -- None of this works for C++. C++ is not C. The first one cannot work, since `sizeof(a)` is a compile-time value, and the second argument to `write` denotes the number of bytes to write. What if there are a million characters in the string? The `sizeof(a)` is not going to be a million. Then the second one -- casting a character buffer to a type does not make that type valid. You must properly construct the `A` object before using it as an object. – PaulMcKenzie Nov 20 '22 at 18:24
  • @PaulMcKenzie first in A, there is no std::string type variable, so it is a fixed size struct. second, since the size if fixed, i think that works, the problem is virtual class – nothingisme Nov 20 '22 at 18:30
  • 2
    @nothingisme -- `A` inherits from `B`, and `B` has a virtual function. Second, and again, casting a buffer to an object does not give you an object. -- *since i have used this method for a long time* -- and code that overwrites a buffer also works "a long time". Does it make the code correct? No. Also, the mere showing of this header: `#include ` shows that you're learning C++ from a poor website. This header should not be used. – PaulMcKenzie Nov 20 '22 at 18:31
  • 1
    [See this](https://godbolt.org/z/P74TPWsbW). If `std::is_trivially_copyable` returns false, then it cannot be used for the way you are using it. – PaulMcKenzie Nov 20 '22 at 18:38
  • 4
    *how can i make it correct?* You can write a serializer and deserializer. – Eljay Nov 20 '22 at 18:42
  • 2
    *since i have used this method for a long time* -- This may be the underlying issue. You've used this code, and so far up until you posted your question, no one had pointed out that it is faulty and cannot work correctly. Now that you have other experienced eyes to look at it and explained the issue, then it should be time to do as @Eljay stated -- write a proper serializer/deserializer, or get a library that does this already. – PaulMcKenzie Nov 20 '22 at 18:45
  • I understand this code cannot work but maybe this change could work : `A a(*(A*)buff); //could work because we know A is the real type write and read cout << a.show_string() << endl;` – jls28 Nov 20 '22 at 18:45
  • 3
    @jls28 -- Again, you cannot cast a buffer to an object. It is undefined behavior doing so. Objects must be constructed before use. – PaulMcKenzie Nov 20 '22 at 18:46
  • @PaulMcKenzie I see no object cast, only supplying (by reference/address) the content of the buffer to (default) copy constructor: Did the (default) copy constructor use the virtual table(s) of the source object ? I think it use only the content of data members from the buffer @nothingisme please test my code. here my test : `A a;` `auto i = 3;` `a.a = i;` `a.b = i - 1;` `alignas(A) char buf[sizeof(A)];` `memcpy(buf, &a, sizeof(A));` `A b(*(A*)buf);` `std::cout << b.a << ' ' << b.b; // print 3 2` – jls28 Nov 20 '22 at 19:47
  • 1
    *I see no object cast, only supplying (by reference/address) the content of the buffer to (default) copy constructor:* -- A copy constructor requires a reference to the object to be passed. No matter what tricks you want to try, you cannot, without undefined behavior, pretend that a buffer is an object by casting. If you want to make a buffer the object, such that `.` and `->` operations work correctly, then you use `placement-new`. That is the only method to inform the C++ compiler that yes, you want that buffer to be an `A` object. – PaulMcKenzie Nov 20 '22 at 19:54
  • 1
    Also, showing code that works is not a valid way to prove that code is valid. As I mentioned in a previous comment, there's *tons* of code out there that have buffer overruns, mismanagement of pointers, etc. and the code "works". It doesn't mean the code is correct. – PaulMcKenzie Nov 20 '22 at 19:58
  • Does this answer your question? [Serializing a class which contains a std::string](https://stackoverflow.com/questions/7046244/serializing-a-class-which-contains-a-stdstring) – Stephen Newell Nov 20 '22 at 20:10
  • The dupe I linked is about `std::string`s, but applies here as well because you have a virtual pointer. – Stephen Newell Nov 20 '22 at 20:10
  • @PaulMcKenzie work for a long time means: i used POD class before, it worked. then i want to make it derived from base class(with virtual), so, the problem comes out, i wont use vector or string in my class, instead i used char[N] and array, in my understanding, this wont cause problem, and currently, the only difference is the virtual pointer, my question actually focus on how to serialize virtual pointer correctly, and this is just a demo, i wont use ```bits/stdc++.h``` in real project. – nothingisme Nov 21 '22 at 01:35
  • @PaulMcKenzie let's get POD agreement first, if the class is POD, there isn't problems here. or there is something i don't know about POD? my initial idea is to make it faster, so i just cast buffer to pointer, not to allocate another memory to construct it. – nothingisme Nov 21 '22 at 01:39

1 Answers1

-1

I think it is because your loader code is calling write instead of read.

Although changing this will work, your A object is inheriting from another class and is no longer trivially copyable (as @PaulMcKenzie stated), so you should instead consider methods to serialize and read your class instead.

Here is a simple example that works on my machine:

#include <iostream>
#include <fstream>
#include <vector>

class A
{
public:
    int a;
    std::string show_string() { return "This is A: " + std::to_string(a); }
};

int main()
{
    // WRITE:
    std::ofstream outFile("a.bin", std::ios::trunc);

    for (int i = 0; i < 10; ++i)
    {
        A a; a.a = i;
        outFile.write((char*)&a, sizeof(A));
    }

    outFile.close();

    // READ:
    std::ifstream inFile("a.bin", std::ios::ate);
    size_t size = inFile.tellg();
    inFile.seekg(0L, std::ios::beg);

    std::vector<A> listOfAs(size / sizeof(A));
    inFile.read((char*) listOfAs.data(), listOfAs.size() * sizeof(A));
    inFile.close();

    // OUTPUT:
    for (auto& i : listOfAs) std::cout << i.show_string() << '\n';
    return EXIT_SUCCESS;
}

Hope this helps.

  • *Here is a simple example that works on my machine:* -- And this works on my machine: `int main() { int *p = new int [10]; delete p; }` -- The code is still not valid, even though it works. – PaulMcKenzie Nov 20 '22 at 19:59
  • @PaulMcKenzie I'm a little confused, are you unhappy because I was casting to a char type? Because ```std::vector``` will construct A as part of its constructor. If there's anything wrong with my code, please let me know so that I can fix it. – UniformSoup Nov 20 '22 at 20:14
  • 1
    The problem is that `A` is not trivially-copyable and is not a POD type, and you're treating it as such. – PaulMcKenzie Nov 20 '22 at 20:17
  • thanks for answering, i think this works. but if there is a virtual function in A, it wont. – nothingisme Nov 21 '22 at 01:42