Reputation: 41
Is there any way to have a general code access members of derived class through base class pointer? Or any other way around this?
Let's say I have a class Shape. I have classes Square and Triangle which inherit it. Both have their own private members which have nothing to do with each other so there is no point in having them in the base class. Now, what if I need to write a class into a file, but I don't know if the class is Square or Triangle until the moment I need to write it in the file?
I've been trying to figure out how to solve this problem. The worst case solution would be to write the data of both Square AND Triangle into a file, add an identifier (Triangle or Square) for both reading and writing and have a small parser put the class together when loading data. This would be inefficient and waste of time.
I was wondering if there is some trick or design pattern or anything that can help with the situation.
Upvotes: 4
Views: 1562
Reputation: 720
I think there is no direct way to remove this overhead. Normally this is done by a two things. First of all, the object needs a serialization mechanism:
To serialize things, one need a location to serialize to. In this case, we will do this using a data container, but this can also be a file stream or a container class. Serialization can be made from within the object or from outside, most easy implementation is now from the inner side:
The simple serialization part:
class Shape{
public:
virtual void serialize( Datacontainer &o ) const = 0;
};
class Triangle: public Shape{
void serialize( Datacontainer &o ) const{
o.add('T');
o.add(corners);
}
std::vector<point> corners;
}
class Circle: public Shape{
void serialize( Datacontainer &o ) const{
o.add('C');
o.add(center);
o.add(radius);
}
point center;
double radius;
}
During serialization, you can do this by using the basic class Shape:
Shape *tri = new Triangle;
tri->serialize( dataContainer or file );
Deserialization is not as easy, because you need to know the type. A good pattern for this is the Builder pattern. Despite this, we can implement a more C++ likely way to do this:
Add the following thing to all of your classes:
static Shape* createFrom( Datacontainer &o );
For eg. the Circle implementation:
Shape* Circle::createFrom( Datacontainer &o )
{
Circle *c = new Circle()
c->center = o.get();
c->radius = o.get();
}
This enables us to create a concrete instance, but we have a common function footprint for the method. Now one can implement a very easy builder like this one:
class ShapeBuilder
{
public:
static Shape* createShape( Datacontainer& o )
{
char id = o.get();
swith(id){
case 'T':
return Triangle::createFrom(o);
case 'C':
return Circle::createFrom(o);
}
}
}
Upvotes: 1
Reputation: 279445
Probably the best way is to do something like this. The basic patten is that you can put common code, that is guaranteed always to be the same for every derived class, in the base. Things that need to differ, put in a virtual function that the derived classes each implement differently.
class Shape {
virtual void writeSerialData(std::ostream &) const = 0;
public:
void writeToFile(const std::string &filename) const {
std::ofstream outfile(filename); // filename.c_str() in C++03
writeSerialData(outfile);
if (!outfile.close()) {
// report the error
}
}
virtual ~Shape() {}
};
class Square : public Shape {
double length;
virtual void writeSerialData(std::ostream &out) const {
out << "Square{" << length << '}';
}
public:
Square(double l) : length(l) {}
};
Now you have the next problem -- how do you read an object back from a file, without knowing in advance which derived class it is? For that you need a way to see the text Square
and either (a) call a static function of the class Square
that knows how to interpret the data or (b) instantiate the class Square by giving it the data to interpret. It's worth looking into Boost Serialization, or other serialization libraries, before you go too far down that path.
Upvotes: 0
Reputation: 13690
This serialization should be done using virtual functions. Define a function in the base class that shall serialize the object. The Triangle and the Square overrides this functions and write
You may implement the common part in the base class if appropriate.
When you want load the file you will need factory method that creates the class instance corresponding to the identifier. The new instance virtual deserialize method must be called to load the actual data.
Upvotes: 8
Reputation: 12904
You can have a pure virtual getter in your Base Class. and all your Derived classes will override that. like this
class Shape{
public:
virtual int data() const = 0;
};
class Square: public Shape{
private:
int _member;
public:
virtual int data() const{
//do something with private members and return it
return _member;
};
};
Upvotes: 3
Reputation: 843
You need to declare virtual methods in your base class, and have derived classes define them. If you want to save them to a file though - you will need a way to identify what specific class instance is in the file, since they may have different memory layouts.
Upvotes: 0