Heritage Shared

Heritage Shared

  • Comments 20

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.

  • Perhaps I don't understand, but it seems illogical that Bar::DoStuff(int a) would hide Foo::DoStuff() since the two functions have different signatures. It also seems strange that a pure virtual function would need a special using statement to be "inherited" in a derived class...

    C++ has too many corner cases, and that is what makes it ugly.

  • Perhaps I don't understand, but it seems illogical that Bar::DoStuff(int a) would hide Foo::DoStuff() since the two functions have different signatures. It also seems strange that a pure virtual function would need a special using statement to be "inherited" in a derived class...

    C++ has too many corner cases, and that is what makes it ugly.

  • Perhaps I don't understand, but it seems illogical that Bar::DoStuff(int a) would hide Foo::DoStuff() since the two functions have different signatures. It also seems strange that a pure virtual function would need a special using statement to be "inherited" in a derived class...

    C++ has too many corner cases, and that is what makes it ugly.

  • Erm, sorry to triple-post. My comment did not post the first few times I tried to submit it.

  • No, it does not make C++ ugly. You should tell your intentions explicitly otherwise future changes in base class can break your code logic because compiler will call new function in base class. It's dangerous.

  • Thanks! I see.

    Is it right that the c++ compiler will missed the pure virtual method which the derived class don't defined?

    ha...

    I always thought the derived class should keep all methods which come from the base.

  • Yes, it will. But actually you can't instantiate abstract class that does not override pure virtual functions.

  • @Unknown

    It is clear that the overloaded function declaration in Bar causes the original in Foo to be discarded, so tony's observation is correct.  It doesn't have anything to do with instantiating abstract classes.  Baz defines both declarations from Foo and Bar, but it cant actually see the Foo declaration until Bar also defines it or forwards it with 'using'.

  • @Chris, the function in Foo is not discarded - it is hidden. What the using declaration does here is make it visible again. You could also call the correct function by qualifying the function at the call site with the type.

  • These corner cases can be easily caught by using a lint program.

    For example here is the result of PC Lint (www.gimpel-online.com/OnlineTesting.html) running over Quiz2 (after removing the not yet standard "override" extension): http://pastebin.com/NZ5YxS4z

    It give this warning:

    Warning 1411:  Member with different signature hides virtual member 'Foo::DoStuff(char)' (line 5)

  • @Nate

    To override it properly in Baz you can't fully qualify it.  You must do one of the two things I said.  I didn't mean discarded in that the function no longer works on the object, more along the lines of name resolution.

  • Cristian, you don't even need lint, the compiler will do this for you at warning level 4, as long as something like MFC hasn't hidden it.

    We turn those warnings back on like this::

    #pragma warning(default: 4263)  // member function does not override any base class virtual member function

    #pragma warning(default: 4264)  // 'virtual_function' : no override available for virtual member function from base 'class'; function is hidden

    #pragma warning(disable: 4266)  // A derived class did not override all overloads of a virtual function.

  • Actually that last one should be default, not disable.  Apparently we haven't been able to turn this one on yet.

  • I've compiled quiz2.cpp (verbatim copy from this page) like:

    $ cl /W4 /EHsc quiz2.cpp

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86

    Copyright (C) Microsoft Corporation.  All rights reserved.

    quiz2.cpp

    quiz2.cpp(16) : warning C4481: nonstandard extension used: override specifier 'o

    verride'

    quiz2.cpp(21) : warning C4481: nonstandard extension used: override specifier 'o

    verride'

    quiz2.cpp(15) : warning C4100: 'a' : unreferenced formal parameter

    quiz2.cpp(20) : warning C4100: 'a' : unreferenced formal parameter

    Microsoft (R) Incremental Linker Version 10.00.30319.01

    Copyright (C) Microsoft Corporation.  All rights reserved.

    /out:quiz2.exe

    quiz2.obj

    It seems that the compiler is not showing any warnings even at warning level 4.

  • I don't understand why the override keyword does not generate a compiler error since DoStuff(char) cannot be overriden?

Page 1 of 2 (20 items) 12