Heritage Shared

A few days ago, we posted two C++ quizzes based on a question posted in a forum. Let’s review the first question

 

Quiz 1
  1. #include <iostream>
  2.   
  3. class Foo {
  4. public:
  5.     virtual void DoStuff()=0;
  6. };
  7.   
  8. class Bar : public Foo {
  9. public:
  10.     virtual void DoStuff(int a)=0;
  11. };
  12.   
  13. class Baz : public Bar {
  14. public:
  15.     void DoStuff(int a) override
  16.     {
  17.         std::cout << "Baz::DoStuff(int)";
  18.     }
  19.   
  20.     void DoStuff() override
  21.     {
  22.         std::cout << "Baz::DoStuff()";
  23.     }
  24. };
  25.   
  26. int main() {
  27.     Baz baz;
  28.     Bar *pBar = &baz;
  29.   
  30.     pBar->DoStuff();
  31. }

 

The guy was frustrated because he expected two things:

  • The code would compile without errors.
  • Line 30 would end up by calling Baz::DoStuff() which in turn would have printed that same in the output console.

Instead, he got the following compile-time error at that same line

e:\foo.cpp(30): error C2660: ‘Bar::DoStuff’ : function does not take 0 arguments

The root of this compilation error is at line 11: as we are closing the definition of class Bar without saying anything about method DoStuff without arguments but, instead, having overloaded DoStuff in line 10 with a version that takes an argument of type int, what we just did was hide the original Foo::Stuff() declaration. With that said, the compilation error makes sense.

The fact that Foo::Stuff() is a pure virtual method is not a necessary condition for this to happen at all. It would have happened with virtual and non-virtual methods as well.

I have the feeling that Java and C# developers may have experienced this when coding artifacts in C++ as, in those languages, this notion of hiding declarations is not available (there’s an alternative consisting in declaring members as private, so subclasses won’t get them visible, but in that case the decision of what is hidden belongs to the coder of the superclass. In C++, the decision is to be taken by the coder of the derived class.

How could my friend overcome this error in order to get the application working as he expected? By including a using declaration in the definition of Bar like the one at line 5 here:

 

  1. class Bar : public Foo {
  2. public:
  3.     // using introduces a name from a base
  4.     // class into a derived class scope.
  5.     using Foo::DoStuff;
  6.     virtual void DoStuff(int a)=0;
  7. };

 

Now the application runs as initially intended.

Quiz 1 running


In quiz 2, the C++ principle we just reviewed applies as well, but if hiding was not what we wanted to do, this issue could turn into something more dangerous because the application will compile anyway and the undesired behavior will have to be discovered at runtime.

 

Quiz 2
  1. #include <iostream>
  2.   
  3. class Foo {
  4. public:
  5.     virtual void DoStuff(char a)=0;
  6. };
  7.   
  8. class Bar : public Foo {
  9. public:
  10.     virtual void DoStuff(int a)=0;
  11. };
  12.   
  13. class Baz : public Bar {
  14. public:
  15.     void DoStuff(int a) override
  16.     {
  17.         std::cout << "Baz::DoStuff(int)";
  18.     }
  19.   
  20.     void DoStuff(char a) override
  21.     {
  22.         std::cout << "Baz::DoStuff(char)";
  23.     }
  24. };
  25.   
  26. int main() {
  27.     Baz baz;
  28.     Bar *pBar = &baz;
  29.   
  30.     pBar->DoStuff('a');
  31. }

 

Despite the fact that Foo::DoStuff(char) isn’t visible in line 30, the ‘a’ received as argument is implicitly converted to the int type, producing:

Implicit conversion

Again, the solution here is based on a using declaration as before:

 

  1. class Bar : public Foo {
  2. public:
  3.     using Foo::DoStuff;
  4.     virtual void DoStuff(int a)=0;
  5. };

 

Once declared, we just compile, run and… voilà

Quiz 2 fixed

 

As a conclusion, hiding a base class method is neither a bad thing nor something to avoid as long as it’s exactly what you wanted to get.