Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple inheritance, virtual methods collision and pointers from base classes

I have a result that I didn't expect from multiple inheritance, virtual methods and pointers to base classes.


With d.getStr(), when d is a derived instance, the base_2 version is called, as I expected.

With p->getStr(), when p is a pointer to a derived instance (or a pointer to base_2 pointing to a derived instance), the base_2 version is called, as I expected.

But with p->getStr(), when p is a pointer to a base_1 pointing to a derived instance, the base_1 version is called and I was convinced would be called the base_2 version (thanks the using and the fact that getStr() are virtual methods).

The following is a simple example:

#include <iostream>

struct base_1
{
   virtual std::string getStr () const
    { return "string from base 1"; }
};

struct base_2
{
   virtual std::string getStr () const
    { return "string from base 2"; }
};

struct derived : public base_1, public base_2
{ 
   using base_2::getStr;
};


int main ()
{
   derived  d;

   derived *  dp  = &d;
   base_1 *   bp1 = &d;
   base_2 *   bp2 = &d;

   std::cout << "from derived:         " << d.getStr() << std::endl;
   std::cout << "from derived pointer: " << dp->getStr() << std::endl;
   std::cout << "from base_1 pointer:  " << bp1->getStr() << std::endl;
   std::cout << "from base_2 pointer:  " << bp2->getStr() << std::endl;
}

The output is the following

from derived:         string from base 2
from derived pointer: string from base 2
from base_1 pointer:  string from base 1
from base_2 pointer:  string from base 2

I know that, to impose the call of base_2 version, I can add in derived the following method

std::string getStr () const
 { return base_2::getStr(); }

but my questions are:

1) Why does the pointer to base_1 (pointing to a derived instance) ignore the using directive and call the base_1 version of getStr()?

2) Is there a way to impose the base_2 version of getStr(), when derived instance is used by a base_1 pointer, without redefining getStr()?

--- EDIT ---

Thanks for the answers.

I understand that you are describing what's happening but my doubt is: does the language (the standard) describe this aspect? Or is it an undefined part?

I mean: if I remove the using directive, I get a compilation error (error: request for member getStr is ambiguous), from d.getStr() and from dp->getStr(), because the compiler doesn't know which version of getStr() to chose.

But getStr() are virtual methods. So (I was convinced that) a base pointer should use the derived version of they. But we have a couple of colliding methods.

From the language (standard) point of view, a base_1 (or base_2) is the pointer authorized (or obligated) to choose one of the two versions of the colliding methods ignoring the other?

Maybe I'm wrong but seems to me that, in this way, the virtual methods are managed as non virtual methods.

like image 331
max66 Avatar asked Jun 19 '16 18:06

max66


People also ask

What is a virtual base class in inheritance?

Virtual base classes are used in virtual inheritance in a way of preventing multiple “instances” of a given class appearing in an inheritance hierarchy when using multiple inheritances. Consider the situation where we have one class A .This class is A is inherited by two other classes B and C.

What is multiple inheritance in C++?

Multiple Inheritance is a feature of C++ where a class can inherit from more than one classes. The constructors of inherited classes are called in the same order in which they are inherited.

What is virtual inheritance in C++?

Virtual inheritance is used when we are dealing with multiple inheritance but want to prevent multiple instances of same class appearing in inheritance hierarchy. From above example we can see that “A” is inherited two times in D means an object of class “D” will contain two attributes of “a” (D::C::a and D::B::a).

What is a virtual base class in Java?

Virtual base classes offer a way to save space and avoid ambiguities in class hierarchies that use multiple inheritances. When a base class is specified as a virtual base, it can act as an indirect base more than once without duplication of its data members.


2 Answers

You are expecting that when you using the using keyword in the following manner:

struct derived : public base_1, public base_2
{ 
   using base_2::getStr;
};

That this is the same as:

struct derived : public base_1, public base_2
{
   void getStr()
   {
        base_2::getStr();
   }
};

In this case, the behavior you are expecting -- invoking p->getStr(), when p is a pointer to a base_1 -- would, indeed, end up invoking base_2::getStr(). derived overrides base_1's getStr(), so invoking base_1's getStr(), through an ordinary pointer, results in derived's getStr() getting invoked, which invokes base_2 getStr() method.

However, this is not what happens. The using keyword is not an alias for forwarding a method call in this manner. The using keyword does not create a method in derived'd class, so class inheritance is not affected, and derived_1's getStr() does not get overriden in the subclsas. And that's why invoking derived_1's getStr() does not wind up invoking derived_2's getStr().

like image 53
Sam Varshavchik Avatar answered Nov 24 '22 10:11

Sam Varshavchik


That happens because the derived class has to have 2 vtable entries for getStr(), one for each base class, so it can correctly resolve both base_1::getStr() and base_2::getStr(). The using directive doesn't create a derived::getStr() vtable entry or replace the base-class ones, it just selects which base-class entry will be used. When going through a pointer to base_1, the compiler only "sees" the vtable entries for virtual functions from derived and base_1 so it resolves getStr() to base_1::getStr(). Your solution of adding an explicit getStr() in derived is probably the cleanest one, although it might be advisable to make it virtual to match the base classes for clarity.

like image 26
Todd Knarr Avatar answered Nov 24 '22 10:11

Todd Knarr