Sharing the Heritage (Quiz Game)

Sharing the Heritage (Quiz Game)

  • Comments 31

I was reading recent discussion threads and found something interesting that someone introduced as a question, but instead of start developing the whole story from here, I’ll just challenge you with two different quiz games.

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. }

Without using Visual Studio or your building tool of preference, just your knowledge, if we tried to run this code, what is expected to see in the console? Why?

Now let’s make a little variation and substitute the method getting no arguments for one getting char, as follows:

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. }

If we ran this new version, provided that it compiles with no errors, what is expected to be seen in the output console? Why?

 

Solutions are explained in this sequel.

  • I don't think Quiz 1 will compile.  The int DoStuff overload hides the void DoStuff overload, so pBar->DoStuff() only sees the int DoStuff overload.  Since pBar->DoStuff() doesn't match DoStuff(int), you get a compile time error.

    Quiz 2 should compile, but it will end up outputting "Baz::DoStuff(int)".  pBar->DoStuff can only see DoStuff(int), and 'a' can convert to an integer, so that overload gets called.

  • Programs won't compile (compiled with /Za) because "override" is a nonstandard extension >:)

  • Well, I think the first should call the Baz::DoStuff(char). As for the second one, I hope Baz::DoStuff(char) is called again but somehow I feel this is one of those cases that the char literal get interpreted as int so I suspect if I actually compile it, Baz::DoStuff(int) gets called.

  • Ah stupid typo. I mean the first one should call Baz::DoStuff() (the non-int version).

  • Won't compile because you missed the return statement at main()

    =P

  • In the second example without "override"  DoStuff(int) should be called.

    Method of derived class (Bar) overrides all base methods with the same name. To call DoStuff(char) you should either:

    - expose it by adding "using Foo::DoStuff;" declration into Bar class or

    - cast pBar to Foo* or

    - specify class name explicitly (e.g. pBar->Foo::DoStuff('a') ).

    Does "override" keyword change this behaviour?

  • Oh, pardon, calling trough Foo::DoStuff will not work:  it would not perfor  virtual call and since Foo::DoStuff is pure virtual function it will not even compile.

  • I hate questions like this because they're never a real world situation. As Midiway pointed out, neither of these examples will compile because they're missing the return from main(). I don't think Quiz 1 should compile either, which means asking what you'll see on the console is misleading. But what's the point of asking a code question without having access to a compiler?

  • In C++98/03/0x and C99 (but not C89), main() (but not any other function) is special. main()'s return type must be int, but falling off its end is guaranteed equivalent to returning 0 (which means success).

    C++03 3.6.1 [basic.start.main]/5: "If control reaches the end of main without encountering a return statement, the effect is that of executing return 0;"

    C99 5.1.2.2.3/1: "reaching the } that terminates the main function returns a value of 0."

    [Cristian]

    > Programs won't compile (compiled with /Za) because "override" is a nonstandard extension >:)

    It is indeed an extension to C++98/03. (This functionality is being added to C++0x, and as of Working Paper N3225 the syntax is identical to VC's with the context-sensitive keyword "override", but the syntax may continue to change.) However, I recommend against compiling with /Za, as it breaks conformant code like vector<unique_ptr<T>>.

  • After removing the 'override', quiz 1 won't compile because Bar::DoStuff(int)

    hides Foo::DoStuff(). Calling DoStuff() with a Bar won't compile, but calling it

    on a Foo or a Baz will. The reasons for name hiding are straight-forward: adding

    a new name in a base class could silently change which function is currently

    being called:

     class A

     {

     public:

       virtual void f(int);

       //virtual void f(float);

     };

     class B : public A

     {

     public:

       virtual void f(int);

     };

     void g(B* b)

     {

       b->f(1.0f);  // calls B::f(int)

     }

    If f(float) is uncommented and name hiding does not apply, b->f(1.0f) silently

    changes.

    Quiz 2 compiles fine and calls Baz::DoStuff(int). Again, because of name hiding,

    DoStuff(char) is not available in Bar. Therefore, 'a' is converted to an int

    and DoStuff('a') calls Baz::DoStuff(int) because it overrides Bar::DoStuff(int).

    To make a name available in a derived class, you can use a using declaration:

     class A

     {

     public:

       virtual void f(int);

       virtual void f(float);

     };

     class B : public A

     {

     public:

       using A::f;

       virtual void f(int);

     };

     void g(B* b)

     {

       b->f(1.0f);  // calls A::f(float)

     }

  • I found this "overload hiding" behaviour of C++ confusing. The C# version of Quiz1 compiles fine and prints "Baz.DoStuff()" (which I find a more intuitive pattern).

  • Stephan T. Lavavej,

    > main()'s return type must be int, but falling off its end is guaranteed equivalent to returning 0

    As you pointed out, this is backed by the standard. (Much to my annoyance.)

    >  (which means success).

    I must take issue with this statement. The standard does not define 0 to mean success. That is why EXIT_SUCCESS and EXIT_FAILURE exist. Note also that the actual values are not defined, but are implementation specific.

  • Not true, Ben. The standard says in [support.start.term]p8: "If status is zero or EXIT_SUCCESS, an implementation-defined form of the status successful termination is returned."

    This in the documentation of exit(int). Furthermore, in [basic.start.main]p5 it says: "A return statement in main has the effect of leaving the main function and calling std::exit with the return value as the argument."

    Thus, returning zero means success.

  • Wow, I was surprised.

    @Parsley72: But what's the point of asking a code question without having access to a compiler?

    IMO the point is to see how the person thinks/tries to figure it out, and how confident they are in their reply. Will somebody who is absolutely confident in this still check to see if they are correct?

  • I think there is no trick in the first one, it will print "Baz::DoStuff()".

    Now the second one will print "Baz::DoStuff(int)", because 'a' is an int constant not a char const (try 'abcd' for example)

    Istvan

Page 1 of 3 (31 items) 123