3
\$\begingroup\$

I have a hierarchical object structure which needs to be converted to JSON. I have a following basic implementation of how it will be done:

#include <iostream>
using namespace std;

class base
{
    public:
    base():a(1){};
    void serialize()
    {
        cout<<"{"<<endl;
        toString();
        cout<<"}";
    };
    virtual void toString()
    {
        cout<<"\"base-a\": "<<a<<endl;
    };

    private:
    int a;
};

class derived: public base
{
    public:
    derived():b(2){};
    virtual void toString()
    {
        base::toString();
        cout<<"\"derived-b\": "<<b<<endl;
    };

    private:
    int b;
};


int main() {
    derived d;
    base& b = d;
    b.serialize();
    return 0;
}

The only problem I see here is when derived is extended then that class' toString function has to call the toString function of derived and so on.

Is calling a overridden function from overriding function a bad practice? Is there a better way to write this code?

Note: I cannot use C++11.

\$\endgroup\$
1
\$\begingroup\$

Is calling a overridden function from overriding function a bad practice? Is there a better way to write this code?

If you are adding serialization support to an existing class hierarchy, then what you are doing is A-O-K.

However, if you are in the process of designing that structure, then it's indirectly considered bad practice.

The whole "subclass to extend" design pattern is frowned upon in general by modern design sensibilities.

If you want to be "modern", then your virtual functions should be pure in the base class, and your class hierarchy should rarely go beyond a single inheritance depth.

The mantra is "prefer composition to inheritance, unless you need type erasure". However, this doesn't mean that everything is fair game as soon as type erasure is needed. You should still strive to limit your use of hierarchy to the minimum required in order to achieve erasure, i.e: Base classes should be pure interface classes, nothing more.

The details on how best to do that really depends on the project, so I can't really go into details here.

Edit: As requested, a more in-depth explanation of composition vs inheritance.

Disclaimer: It's a fairly subtle, almost academic, subject.

The theory goes like this:

  1. Apart from type erasure, there is nothing gained by inheritance that cannot be accomplished through composition.

  2. Composition is more flexible. Primarily because you don't run the risk of running into clashing namespaces, but also because diamond patterns are easier to work with.

A good example of this is shared_ptr<> vs having a RefCounted base class at the bottom of your hierarchy.

With shared_ptr<>, which is composition-based, you get a few major benefits:

  1. You can apply the feature to anything, even base types.
  2. You won't run into a diamond pattern if you need to inherit from two interfaces.
  3. You don;t pay for the feature until you are actually using it.
\$\endgroup\$
  • \$\begingroup\$ I am designing the class hierarchy myself. \$\endgroup\$ – masterop Sep 20 '17 at 8:11
  • \$\begingroup\$ You did not mention any pros and cons of either of the approach. What do you mean by "modern", can you please elaborate and give some concrete examples? The design pattern in question is "Template method". \$\endgroup\$ – masterop Sep 20 '17 at 8:13
  • \$\begingroup\$ @masterop I've updated my answer with a basic explanation, and simple example. \$\endgroup\$ – Frank Sep 20 '17 at 14:03
0
\$\begingroup\$

As programmers we use the tools and patterns that we are used to, calling a member of the superclass is a common pattern when creating inheritance structures. In your case this is perfectly suitable. One of the real issues with 'requiring' to call a superclass method when implementing a derived method is that its easy to forget that call when writing the overwritten function. So in general it is something I try to avoid. Converting to JSON as you describe it is also know as serialization. With small examples like this it is often hard to chose a right approach as there are very few indication to the requirements. When you get to address larger problems the requirements of the system, or even the architecture of the system that you are adding to will indicate the approach.

There are other ways to write this code in c++ some would depend on outside libraries or c++11/14/17 functionality. For example if you don't have to deal with multiple types (string, int, etc.) in in your data you could build a std::unordered_map<int> of the data in your classes and convert the map rather than the classes themselves. Each subclass could then add it's data to the map in a setter function which means it doesn't have to call the superclass methods anymore.

Another way (more complicated, but more flexible way) would be to write each subclasses serialization function independent of the superclass but collect all the toString function pointers in a list in the baseclass, the baseclassesserialize()function can now call the subclasstoString()functions from that list. Function pointers are much easier to handle withstd::functionstd::bind` or lambda function from c++11 and above.

Some libraries supply reflection mechanisms. Reflection is a way for a class to supply information about itself. E.g. list of setters and getters, or specific type information. When reflection is available, serialization becomes much easier to write.

Some other notes ...

toString() doesn't do what is says, if I see this function I would expect it to be std::string A::toString(), with regard to your API, you probably want to change your interface for serialize and toString() to take an std::ostream& this way you are not bound to write to std::cout. You could also just return a string from serialize() and let the caller decide what to do with the string.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.