Solved

C++ Class Serialization, Encapsulation and Formatted Output

Posted on 2016-09-14
8
43 Views
Last Modified: 2016-09-21
With just the std library, and being a C++ noob, I am wondering the following:

  1. Serialization:  What is the best way to serialize my class?  I have a Date class and was thinking of using << to serialize it to something in the format of YYYYMMDD. I would deserialize it by one of the constructors taking that format for a Date and creating the same valued object.
  2. Encapsulation & Formatted Output: I've been reading about true encapsulation in classes (have read about it for years) and some suggest not exposing anything in terms of the innards so it won't be tightly coupled. So for example, if I do use << to serialize, how do I offer the ability to output the date to the user in various formats? If I didn't use << for serialization, how would allow different output formats of the date also? What is the normal best practice for that?

While EE will want inline answers, Any links or books recommendations (in addition) would also be appreciated.
0
Comment
Question by:SStory
  • 4
  • 4
8 Comments
 
LVL 32

Accepted Solution

by:
sarabande earned 500 total points
ID: 41798436
assume you have a class Contact defined as

class Contact
{
       Person person;
       Address address;
       Date    created;
       Date    lastvisited;
       Date    nextcall;
public:
       int Serialize(SerialBuffer & buf);
       Contact(SerialBuffer & buf);
};

Open in new window


if you want to serialize it, you need to serialize the class members, for example

class Person
{
     std::string firstName;
     std::string lastName;
     Gender gender;
     Title title;
     Phone phone;
     Phone mobile;
public:
     int Serialize(SerialBuffer & buf);
     Person(SerialBuffer & buf);
};

Open in new window


and class members of Person as well:

class Gender
{
      char gender;
public:
     int Serialize(SerialBuffer & buf);
     Gender(SerialBuffer & buf);
};

class Gender
{
      char gender;
public:
     int Serialize(SerialBuffer & buf);
     Gender(SerialBuffer & buf);
     char GetChar() const { return gender; }
};

class Title
{
      std:.string title;
public:
     int Serialize(SerialBuffer & buf);
     Title(SerialBuffer & buf);
     std::string GetStr() const { return title; }
     
};

class Phone
{
      std::string phone;
public:
     int Serialize(SerialBuffer & buf);
     Phone(SerialBuffer & buf);
     std::string GetStr() const { return phone; }

};

Open in new window


and so on:

then we look at SerialBuffer class

class SerialBuffer 
{
      size_t init;
      size_t pos;
      std::vector<char> buf;
public:
      SerialBuffer(size_t initsiz = 0) : init(initsiz), pos(0) { if (init > 0) buf.reserve(init); }
      int GetPos() const { return (int)pos; }
      SerialBuffer & operator<<(const std::string & val)
      {
            size_t required = pos +  sizeof(size_t) + val.length();
            if (required > buf.size())
            {
                  buf.resize(required);
            }
            // copy length of string
            memcpy(&buf[pos], &val.length(), sizeof(size_t));
            // copy string
            pos += sizeof(size_t);
            memcpy(&buf[pos], &val.c_str(), val.length());
            pos = required;
            return *this;
      }            
      SerialBuffer & operator<<(const char & val)
      {
            size_t required = pos + 1;
            if (required > buf.size())
            {
                  buf.resize(required);
            }
            // copy char
            buf[pos] = val;
            pos = required;
            return *this;
      }   
      SerialBuffer & operator<<(Date & val)
      {
            std::string datestr = val.Format("YYYYMMDD");
            (*this) << datestr;
            return *this;
      }   
};

SerialBuffer & operator<<(SerialBuffer & buf, const Gender & gender)
{
       buf << gender.GetChar();
       return buf;
}

SerialBuffer & operator<<(SerialBuffer & buf, const Gender & gender)
{
       buf << gender.GetChar();
       return buf;
}
SerialBuffer & operator<<(SerialBuffer & buf, const Title & title)
{
       buf << title.GetStr();
       return buf;
}
SerialBuffer & operator<<(SerialBuffer & buf, const Phone & phone)
{
       buf << title.GetStr();
       return buf;
}

Open in new window


with the above you can implement Person::Serialize like

int Person::Serialize(SerialBuffer & buf)
{
       buf << firstName << lastName << gender << title << phone << mobile; 
       return buf.GetPos();
} 

Open in new window


or if you like better (and added the const getters)

SerialBuffer & operator<<(SerialBuffer & buf, const Person & Person)
{
       buf << person.GetFirstName() 
             << person.GetLastName() 
             << person.GetGender() 
             << person.GetTitle() 
             << person.GetPhone() << person.GetMobile(); 
       return buf;
}

Open in new window

     

finally you could have like

int Contact::Serialize(SerialBuffer & buf)
{
      buf << person << address << created << lastVisited << nextcall;
      return buf.GetPos();
} 

// deserialize
Contact::Contact(SerialBuffer & buf)
{
      buf >> person >> address >> created >> lastVisited >> nextcall;
} 

Open in new window


of course for the last you would need appropriate operator>> functions.

Sara
0
 
LVL 25

Author Comment

by:SStory
ID: 41798597
sarabande,

Thanks for your responses and for bearing with these noob questions. Of course if I were clear on it I wouldn't have asked to start with ;)

Contact(SerialBuffer & buf);

Open in new window

Is that a constructor that takes a SerialBuffer to deserialize?

SerialBuffer & operator<<(SerialBuffer & buf, const Gender & gender)
{
       buf << gender.GetChar();
       return buf;
}

Open in new window

If I understand this correctly, for every class  known to man I'd need to open SerialBuffer and define operator << for that class.  Do I misunderstand? is there not a better way that avoids this?
If I am correct, wouldn't it be better if the SerialBuffer class only serialized the basic built in types and when passed to a class it used that to serialize?

I know if vb.net there is an idea called an interface where a class implements x.
Example:
Implements ISerializable

I'm trying to figure it out before going too far. At present I have only written 3 classes, and wanted to know best practice before trying to serialize them.  


This is a contract that says that any class that states that must implement all methods in the contract (interface).  Is there anything equivalent to this in C++?

std::string datestr = val.Format("YYYYMMDD");

Open in new window

Is this Format function using BOOST or something? I was trying to stay std only at least for the near term.
0
 
LVL 32

Assisted Solution

by:sarabande
sarabande earned 500 total points
ID: 41799218
Is that a constructor that takes a SerialBuffer to deserialize?
yes. alternatively (and probably better) you may construct an empty object and use SerialBuffer::operator>> to "fill" the members.

I'd need to open SerialBuffer and define operator << for that class.  Do I misunderstand?

no. in c++ however you additionally can define global operator>> functions which take a SerialBuffer by reference as first argument and an arbitrary class as input object. it is the same principle as for any stream class where you can add streaming operators without needing to change the stream class itself.

you can define those operators directly as friend operators in your class like

class Person
{
     ...
public:
     friend SerialBuffer & operator<<(SerialBuffer & buf, const Person & p)
     {
           buf << p.firstName << p.lastName << ....;
           return buf;
     }
     friend SerialBuffer & operator>>(SerialBuffer & buf, const Person & p)
     {
           buf >> p.firstName >> p.lastName << ....;
           return buf;
     }
};

Open in new window


Interface classes have other purpose. they define access to huge abstract systems like COM or .NET. the streaming concept with SerialBuffer is using just stl and c++ as you required. if you want to go beyond you better look first to boost which offers advanced serialization for c++.



by using friend functions you spare the get and set functions.

wouldn't it be better if the SerialBuffer class only serialized the basic built in types
no, you should do both to get the full advantage of streaming.

of course each operator function easily could be replaced by a Serialize (Deserialize) function. but it is much less elegant than to simple add all members like in

buf << person << address << nextcall;

Open in new window


the Date::Format function is only a sample that should show that formatting a date can be made simple also for serialization if you want. it depends on which date class you were using, but surely formatting a date for output is a simple task and an independent class functionality.

Sara
1
 
LVL 25

Author Comment

by:SStory
ID: 41799745
Sara, I am trying to get my head around this, so bare with me just a bit more:
class Person
{
     ...
public:
     friend SerialBuffer & operator<<(SerialBuffer & buf, const Person & p)
     {
           buf << p.firstName << p.lastName << ....;
           return buf;
     }
     friend SerialBuffer & operator>>(SerialBuffer & buf, const Person & p)
     {
           buf >> p.firstName >> p.lastName << ....;
           return buf;
     }
};

Open in new window

In the above code, how do I call that? Is it like below???

SerialBuffer buf;
Person person(whatever to init);

//is this how it would look to serialize
buf << person; ???

//and to deserialize??

Open in new window

I am a bit new to operator overloading and the syntax of it.
If I am understanding correctly the << >> operator overloading would be on the SerialBuffer class
and not on Person, yet defined in Person for use with SerialBuffer as you have shown.

Then the normal << that can be used by cout or whatever would still function because it using the definition given by an ostream?

As to the .Format()
I guess my real question was, since from examples I'd seen output being like
cout << mydate;
I wondered how I would do that and get formatted output.  So apparently I'd just not use the default << for mydate.  With .format, how would I get it to cout?  Would I somehow overload << to be able do some sort of thing like
cout << mydate.formatted("YYYYMMDD") or something like that? If so would you mind showing me an example?

I sincerely appreciate your time and help as I try to understand and learn this.
0
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 
LVL 32

Assisted Solution

by:sarabande
sarabande earned 500 total points
ID: 41799999
how do I call that? Is it like below???

yes.

and to deserialize??

similar, beside that you need to get a filled SerialBuffer from somewhere:

SerialBuffer buf;
buf << Person(.....);

Person p; // empty Person

buf >> p; // p now is filled

// alternatively

Person p2(buf);   // if you provided an appropriate constructor

Open in new window


note, SerialBuffer is not yet complete regarding deserializing. you need to reset 'pos' member to 0 before you can deserialize. or, maybe better, have two different pos members for serializing and deserializing.

If I am understanding correctly the << >> operator overloading would be on the SerialBuffer class and not on Person, yet defined in Person for use with SerialBuffer as you have shown.
it is that the operator<< and operator>> which have a SerialBuffer as argument are not member functions but global functions while the streaming operators that are defined in class SerialBuffer with one argument are true member functions. nevertheless, for the compiler they were used same way. it doesn't make a difference whether you make the global operators friend of the data class or not. as told, the advantage is that you can make private members and don't need getters and setters for the serializing only. it also isn't a bad place if you define them directly in the class they were serving for.

Then the normal << that can be used by cout or whatever would still function because it using the definition given by an ostream?
actually, both techniques are identical and are 'normal' streaming techniques. you also can supply global operator<< functions for use with cout (ostream).

cout << mydate; I wondered how I would do that and get formatted output.

if your Date class is defined like

class Date
{
     int date;
public:
     Date(int y, int m, int d) : date(y*10000+m*100+d) {}
     std::string Print() 
     {  
          std::ostringstream oss;
          oss << std::setw(2) << std::right << std::setfilled('0') << date%100 << '/'
                 << std::setw(2) << std::right << std::setfilled('0') << (date/100)%100 << '/'
                 << std::setw(4) << std::right << std::setfilled('0') << (date/10000);
          return oss.str();
     }
};

Open in new window


you simply can do

    cout << Date(2016, 9, 15).Print() << std::endl;

and get proper output.

same would work with a Format function, which could be implemented by using c runtime function strftime or by using a self-written formatter.

actually, it is quite independent from serializing. serializing simply needs a representation of the date which can be deserialized. for that the length of piece of buffer that contains a serialized date must be known. if you do yyyymmdd or dd/mm/yyyy doesn't matter. you even could store it binary as an integer.

Sara
0
 
LVL 25

Author Comment

by:SStory
ID: 41805405
Sara,

What do you mean by global function?  Public method on Person? or what?  If not where does this global function go?

Also, when you answer the global question above, I will award you the points. I have decided to ask more questions as a new question. In case you wish to participate the link is below:

https://www.experts-exchange.com/questions/28970873/Better-understanding-on-C-Class-serialization-and-formats.html2897087328970873
0
 
LVL 32

Assisted Solution

by:sarabande
sarabande earned 500 total points
ID: 41806282
What do you mean by global function?
global functions in c++ are non-member functions and static member functions.

so, the streaming operator functions with two arguments are global functions. they can be defined (implemented) outside of class scope. even if you implement them as a friend function within a class, it is still a global function.

Sara
0
 
LVL 25

Author Comment

by:SStory
ID: 41808871
Thank you so much for your time and patience! BTW the other link doesn't work. Here is the link to the new questions:
HERE
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

When writing generic code, using template meta-programming techniques, it is sometimes useful to know if a type is convertible to another type. A good example of when this might be is if you are writing diagnostic instrumentation for code to generat…
Many modern programming languages support the concept of a property -- a class member that combines characteristics of both a data member and a method.  These are sometimes called "smart fields" because you can add logic that is applied automaticall…
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now