Hacker News new | ask | show | jobs
by IvoDankolov 4768 days ago
Yes, you are missing something, which is a bit of a quirk of C++. Normally, having two functions with the same signature in a parent and child class results in overloading.

  struct A 
  {
      void f() {std::cout << "A";}
  };

  struct B : A
  {
      void f() {std::cout << "B";} 
  };
  //.,...
  A a = B();
  B b = B();
  a.f();
  b.f();
  b.A::f();
This would print ABA, as the child's f() function simply hides the parent one when working in the context of B(), but it does not override it.

Now, what virtual does is it tells the compiler, "from here on out, resolve clashing signatures for this function in child classes by overriding. I'm sure you know how our example changes when you mark f() as virtual.

That's all old news, though. The more interesting bit, final, acts like so: from here on out, prevent overloading in child classes. Now, that only makes sense in the context of virtual, so that's the only place where you're allowed to use final, and here the actual difference to the first example becomes apparent. You see, final does not negate virtual, what it actually does is completely lock up the function signature from being used in any child classes. Adding virtual final to our example would not cause B's function to hide A's - it will simply not compile.

If you think that's all pointless semantics, you are absolutely right. Locking up names is not the point of "final". In fact, declaring something as virtual final in the base class is completely pointless from any practical standpoint. The actual problem that final is meant to solve is this:

As I mentioned earlier, virtual changes the way resolving names works in child classes forever. These two pieces of code are absolutely equivalent.

  struct A 
  {
      virtual void f() {std::cout << "A";}
  };

  struct B : A
  {
      void f() {std::cout << "B";} 
  };
And:

  struct A 
  {
      virtual void f() {std::cout << "A";}
  };

  struct B : A
  {
     virtual void f() {std::cout << "B";} 
  };
Therein lies an interesting dilemma. What if I wanted to prevent any child class of B, and only B, from changing the implementation of f. Under C++03, that is not possible. In C++11, final solves that.

But why go through the trouble of introducing a new keyword - and a keyword that is only a keyword in class and function declarations to boot (Holy context dependent grammar, Batman!) - and not just drop the virtual qualifier? Backwards compatibility - ever a dreaded thing when you wish you could undo your old mistakes.

It isn't all that big of a deal (said the C++ developer about every strange rule in the language, ever), though, since when you use "final" for the intended purpose, you won't actually be needing the virtual qualifier.

  struct A 
  {
      virtual void f() {std::cout << "A";}
  };
  
  struct B : A
  {
      void f() final {std::cout << "B";} 
  };
  
  struct C : B
  {
      void f(); //I'm afraid I can't let you do that.
  };