0

I'm trying to learn how to serialize C++ objects. After reading couple of posts here, it seems a nice idea to utilize boost serialization functions and archiving using load/save functions. However, I would like to avoid using boost library.

Conceptually, can I save an object without knowing its fields. With no reflection in C++, is the only way to store an object is to know all its class members.

Can using stringstream and overloading the << operator to convert an object to a string, can I directly save an object.

Thanks, K.

3
  • 2
    No. In a language without reflection, you can't do anything with the members unless you know what those members are. Commented Aug 2, 2016 at 0:25
  • 2
    Someone has to know its fields, possibly itself. You can make it so that the caller does not need to know the fields, and that there is one function for both reading and writing. Note that serialization has lots of thorny issues, like versioning and the like, that make one-size-fits-all libraries questionable. Commented Aug 2, 2016 at 0:28
  • I believe what your real question might be "I don't want to implement serialization". Find a library like boost.serializatoin to help you. Commented Jul 4, 2022 at 13:03

3 Answers 3

1

Conceptually, can I save an object without knowing its fields.

No, you can't.

With no reflection in C++, is the only way to store an object is to know all its class members.

Yes. The best way is to leave that knowledge encapsulated in that class itself:

 class MyClass {
 public:
     std::ostream& put(std::ostream& os) const {
         os << field1 << " " << field2 << std::endl;
         return os;
     }
     friend std::ostream& operator<<(std::ostream& os, const MyClass& myclass) {
         return myClass.put(os);
     }
 private:
     int field1;
     double field2;
 };
Sign up to request clarification or add additional context in comments.

4 Comments

I dislike this design because read write violates dry. Add a helper template and have io be one function with read version reading and write version writing to improve things.
@Yakk You can always add more levels of indirection, that doesn't prevent you from knowing the actual fields are there (QED here).
This gets complicated very quickly (e.g. imagine if the class had a string field) - I think it'd better to implement put by calling a helper function for each named member, and the helper is overloaded to support whatever member types you will be using
@M.M How does a std::string field complicates this simple way actually? If you mean some more sophisticated delimiters besides the simple ' ', these can be easily be added/changed.
0

An approach you can follow is to support tuple-likes and iterables by default.

Have a read_archive and write_archive that first does fancy sfinae to detect types that support for(:) loops, and types that are tuple-like in that they support std::tuple_size and ADL get<I>.

This also lines up with one form of structured binding support in C++17.

Next, have a to_tie sfinae adl enabled based implementation, that checks to_tie() and to_tie(.). If so and read/writable, uses that.

Maybe somewhere in there include an adl lookup of an archive_io function, so you can explicitly write your own io.

The goal is to auto-archive as much as possible, and what you cannot make it easy to compose with.

Comments

0

If you want to copy the object as it is like a true snapshot, we can read the bytes in the object one by one and store them as characters and paste them onto another object or save it in a file. This is a rough idea:

#include <iostream>
#include <iomanip>

struct Serialization
{
    std::string data;
    template <class T>
    void in(const T &obj)
    {
        auto size = sizeof(T);
        const char *ptr = (const char *)&obj;

        for (size_t i = 0; i < size; i++)
            data.push_back(*(ptr + i));
    }
    template <class T>
    void out(T &obj)
    {
        char *ptr = (char *)&obj;
        for (size_t i = 0; i < data.size(); i++)
            *(ptr + i) = data[i];
    }

    void clear() { data.clear(); }
};

We don't know what is inside the object, it just gets copied as it is. This is somehow easy to use because shallow-copies any object, but also not desirable when

  • There are pointer family members (raw pointers, smart pointers, std::vector,...) that own some memory out of the object. Only pointers are copied but the target memories won't be copied.

  • It also copies a class's vtable pointer which I don't know if it would be useful at all.

Examples:

class person
{
    int age;
    char name[3];
    float height;
public:
    person() = default;
    person(int a, char n[3], float h) : age{a}, height{h}
    {
        for (size_t i = 0; i < 3; i++)
            name[i] = n[i];
    }
    void print()
    {
        std::cout << age << " " << name[0] << name[1] << name[2] << " " << height << "\n";
    }
};

int main()
{
    char x[3] = {'1', '2', '3'};
    person m{12, x, 180};
    person n;
    Serialization s;
    s.in(m);
    s.out(n);
    n.print(); // 12 123 180

    s.clear();
    double d;
    s.in(1.2345678910);
    s.out(d);
    std::cout << std::setprecision(10) << d; // 1.234567891

    return 0;
}

The Serialization class can be extended to serialize multiple identical/different objects by putting some conversions for writing strings, like repetitions of

 object name size => object name => object data size => object data

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.