dynamic_cast and multiple inheritance
The setup
You have an interface, which defines some pure virtual method, and a set of classes that derive from it:
struct interface {
virtual void foo() = 0;
virtual void bar() = 0;
virtual ~interface() = default;
};
struct derived1 : interface{
void foo() override;
void bar() override;
void baz();
};
struct derived2 : interface{
void foo() override;
void bar() override;
void baz();
};
struct derived3 : interface{
void foo() override;
void bar() override;
};
// other classes
Then there is a function that takes an interface*
, and the implementation calls some function on it:
void fun(interface* i){
i->bar();
i->foo();
};
A problem arises
Now some classes (let’s say derived1
and derived2
), even if they implement the interface, are more special than others, and you would like to treat them differently inside of fun
.
There are two possibilities:
-
Extend
interface*
by adding member functions that are relevant only forderived1
andderived2
-
add a
dynamic_cast
Extending interface*
might not be possible, as the class might be defined in another library. Also changing the signature of fun
should be avoided, as it should still handle all possible interface*
(and it might be a callback, so we simply cannot change it)
dynamic_cast
dynamic_cast
is normally presented as the tool to cast down the class hierarchy, and would solve the issue in this case:
void fun(interface* i){
{
auto ptr = dynamic_cast<derived1*>(i);
if(ptr){
// do something special with ptr
return;
}
}
{
auto ptr = dynamic_cast<derived2*>(i);
if(ptr){
// do something special with ptr
return;
}
}
i->bar();
i->foo();
};
Multiple inheritance to the rescue
The more special cases you have, the more verbose the code gets. It would be nice if there would be a way to reduce such code duplication.
derived1
and derived2
are different from the other classes, but it might be possible to encapsulate such functionality in a member function baz
.
If this is possible, we can remove some duplication by adding another interface!
struct interface {
virtual void foo() = 0;
virtual void bar() = 0;
virtual ~interface() = default;
};
struct interface2 {
virtual void baz() = 0;
virtual ~interface2() = default;
};
struct derived1 : interface, interface2 {
void foo() override;
void bar() override;
void baz() override;
};
struct derived2 : interface, interface2 {
void foo() override;
void bar() override;
void baz() override;
};
struct derived3 : interface {
void foo() override;
void bar() override;
};
// other classes
void fun(interface* i){
auto ptr = dynamic_cast<interface2*>(i);
if(ptr){
ptr->baz();
return;
}
i->bar();
i->foo();
};
Thanks to interface2
, it is possible to remove the duplicated logic that was inside fun
.
This might look like a magic trick, interface
and interface2
are two separate classes, and both have no idea of the other class.
Yet it is possible to dynamic_cast
one class to the other (in both directions).
This is because dynamic_cast
is not only useful for "downcasting", ie casting from a base class to a derived class.
It can be also used for casting a derived class to a base class (unnecessary as this conversion is implicit).
And it can be used, like in this example, for casting up and down the class hierarchy at the same time.
Was multiple inheritance necessary?
No, it would also have sufficed to add an intermediate class in a single-file hierarchy
struct interface {
virtual void foo() = 0;
virtual void bar() = 0;
virtual ~interface() = default;
};
struct interface2 : interface {
virtual void baz() = 0;
};
struct derived1 : interface2 {
void foo() override;
void bar() override;
void baz() override;
};
struct derived2 : interface2 {
void foo() override;
void bar() override;
void baz() override;
};
struct derived3 : interface {
void foo() override;
void bar() override;
};
// other classes
void fun(interface* i){
auto ptr = dynamic_cast<interface2*>(i);
if(ptr){
ptr->baz();
return;
}
i->bar();
i->foo();
};
The code inside fun
is unchanged, and derived1
and derived2
need to specify only a base class.
In such minimal examples, there is no difference, but in a bigger project, having interface2
not depending on interface
can help to avoid unnecessary dependencies between classes, projects, and/or libraries.
Deep public virtual hierarchies confound me, so I might prefer the solution with two base classes to keep it flatter, but I suppose that having N
base classes has the same complexity as having a class hierarchy N
level deep, so I do not think that the stylistic difference is that relevant.
Another possible advantage of having multiple base classes is that they can have different visibilities.
Thus one can have a public interface
base class and a private interface2
base class.
Even if private inheritance is useful, in this case, it would not be useful as the dynamic_cast
would fail (return nullptr
).
Obviously, multiple inheritance has disadvantages too, like
-
having two base classes defining the same function signature to be overridden
But for this use case, extrapolating a new interface from a class hierarchy, those are non issues.
Do you want to share your opinion? Or is there an error, some parts that are not clear enough?
You can contact me anytime.