Solution of the Diamond Problem In C++


Object Oriented C++ নিয়ে পড়াশোনা শুরুতেই একটা মজার প্রবলেম পেলাম। আমি JAVA’তেই কাজ করেছি সবসময় এবং দেখেছি JAVA multiple inheritance support করে না(তবে Interface’এর মাধ্যমে multiple inheritance’এর flavor পাওয়া যায়)। এইটা একটা অসুবিধার মতই ভাবতাম আমি। কিন্তু C++ জানতে গিয়ে আবিস্কার করলাম JAVA কেন multiple inheritence Support করে না। তা হল JAVA তৈরী করার সময় যে ক’টি বিষয়ে জোর
দেয়া হয়েছিল তার অন্যতম হচ্ছে make JAVA Simple. ত কিভাবে  multiple inheritance program’কে complex করে তুলতে পারে তার একটা উদাহরন হচ্ছে C++’এর diamond problem। নিচের ছবিটি দেখি-

Diamond problem in C++

diamond problem in C++

এখানে A inherit করেছে B and C ‘কে, C and B inherit করেছে D’ কে। এখন A যদি D object’ কে call করে তাহলে সে কোন অব্জেচত পাবে? যে D object B inherit করেছিলো নাকি যে D object C inherit করেছিলো? এইটাই হল মজার diamond problem. নিচে কিভাবে C++ এই সমস্যার সমাধান করে তা দেখানো হয়েছে। নিচের Article আমি নেট থেকে পেয়েছি। লিঙ্ক দিয়ে দিলাম, চাইলে direct link  থেকেই পড়ে নিতে পারেন।

Solving the Diamond Problem with Virtual Inheritance

By Andrei Milea

Multiple inheritance in C++ is a powerful, but tricky tool, that often leads to problems if not used carefully. This article will teach you how to use virtual inheritance to solve some of these common problems programmers run into. If you’re not familiar with multiple inheritance, check out this article on multiple inheritance.

The diamond problem

One of the problems that arises due to multiple inheritance is the diamond problem. A classical illustration of this is given by Bjarne Stroustrup (the creator of C++) in the following example:

class storable //this is the our base class inherited by transmitter and receiver classes
{
        public:
        storable(const char*);
        virtual void read();
        virtual void write();
        virtual ~storable();
        private:
        ....
}

class transmitter: public storable
{
        public:
        void write();
        ...
} 

class receiver: public storable
{
        public:
        void read();
        ...
}

class radio: public transmitter, public receiver
{
        public:
        void read();
        ....
}

Since both transmitter and receiver classes are using the method write() from the base class, when calling the method write() from a radioobject the call is ambiguous; the compiler can’t know which implementation of write() to use, the one from the transmitter class or the one from the receiver class.

To understand how this is works, let’s take a look at how the objects are represented in memory. Inheritance simply puts the implementation of two objects one after another, but in this case radio is both a transmitter and a receiver, so the storable class gets duplicated inside the radio object. The g++ compiler will complain when compiling the code: error: ‘request for member “write” is ambiguous’, because it can’t figure out whether to call the method write() from storable::receiver::radio or from storable::transmitter::radio.

Fortunately, C++ allows us to solve this problem by using virtual inheritance. In order to prevent the compiler from giving an error we use the keyword virtual when we inherit from the base class storable in both derived classes:

class transmitter: public virtual storable
{
        public:
        void read();
        ...
} 

class receiver: public virtual storable
{
        public:
        void read();
        ...
}

When we use virtual inheritance, we are guaranteed to get only a single instance of the common base class. In other words, the radio class will have only a single instance of the storable class, shared by both the transmitter and receiver classes. By having a single instance of storable, we’ve resolved the compiler’s immediate issue, the ambiguity, and the code will compile fine.

Memory Layout in Virtual Inheritance

In order to keep track of the single instance of the storable object, the compiler will provide a virtual function table (vtable) for classes transmitterand receiver. When a radio object is constructed, it creates one storable instance, a transmitter instance and a receiver instance. The transmitterand receiver classes have a virtual pointer in their vtables that stores the offset to the storable class. When the transmitter class or the receiverclass goes to access any fields of the storable, it uses the virtual pointer in its vtable to find the storable object and find the field in it. This tutorialoffers a comprehensive explanation of the memory layout of virtual inheritance in GCC.

Constructors and Virtual Inheritance

Because there is only a single instance of a virtual base class that is shared by multiple classes that inherit from it, the constructor for a virtual base class is not called by the class that inherits from it (which is how constructors are called, when each class has its own copy of its parent class) since that would mean the constructor would run multiple times. Instead, the constructor is called by the constructor of the concrete class. In the example above, the class radio directly calls the constructor for storable. If you need to pass any arguments to the storable constructor, you would do so using an initialization list, as usual:

radio::radio ()
    : storable( 10 ) // some value that storable needs
    , transmitter()
    , receiver()
{}

One thing to be aware of is that if either transmitter or receiver attempted to invoke the storable constructor in their initialization lists, that call will be completely skipped when constructing a radio object! Be careful, as this could cause a subtle bug!

By the way, the constructors for virtual base classes are always called before the constructors for non-virtual base classes. This ensures that a class inheriting from a virtual base class can be sure the virtual base class is safe to use inside the inheriting class’s constructor.

The destructor order in a class hierarchy with a virtual base class follows the same rules as the rest of C++: the destructors run in the opposite order of the constructors. In other words, the virtual base class will be the last object destroyed, because it is the first object that is fully constructed.

Delegating to a sister class

A powerful technique that arises from using virtual inheritance is to delegate a method from a class in another class by using a common abstract base class. This is also called cross delegation. Let’s assume we have a similar scenario like in the diamond example, with small changes. Suppose the write() method in transmitter class needs to access the read() method from receiver for the radio to work (this is kind of a weird behavior, but let’s take it for the sake of illustration) :

class storable
{
        public:
        storable(const char*);
        virtual void read()=0; //this becomes pure virtual making storable an abstract
        virtual void write(); //class
        virtual ~storable();
        private:
        ....
}

class transmitter: public virtual storable
{
        public:
        void write()
        {
                read();
                ....
        }
} 

class receiver: public virtual storable
{
        public:
        void read();
}

class radio: public transmitter, public receiver
{
        public:
        ...
}

int main()
{
        radio *rad = new radio();
        receiver *r1 = rad;
        transmitter *r2 =rad;

        rad->write();
        r1->write();
        r2->write();
        return 1;
}

Because of virtual inheritance, when the write() function from the transmitter class is called, the method read() from the receiver class gets called (as you may have noticed, the transmitter class doesn’t have a read() function). In the above hierarchy we can instantiate only the radio class because transmitter and receiver are abstract due to virtual inheritance.

Other considerations when using multiple inheritance in C++

When you use a class that is based on virtual inheritance like radio, you should avoid using C style casts and use the C++ specific dynamic_cast instead (more information on casting). It will perform a runtime check for validity before casting, so you can be sure that the type of the object you want to cast is related (by inheritance) with the object type you want to cast into. If they are not related, the result will be a NULL pointer or a bad_cast exception in case of references.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s