4

I happened to be browsing the source for mongoDB, and found this interesting construct:

class NonspecificAssertionException final : public AssertionException {
public:
    using AssertionException::AssertionException;

private:
    void defineOnlyInFinalSubclassToPreventSlicing() final {}
};

How does the private method prevent slicing? I can't seem to think of the problem case.

Cheers, George

1 Answers1

4

The only member functions to which the final specifier may be applied are virtual member functions. It is likely that in AssertionException or one of it's own base classes, this member is defined as

virtual void defineOnlyInFinalSubclassToPreventSlicing() = 0;

Thus, all classes in the hierarchy save the most derived ones are abstract base classes. One may not create values of abstract classes (they can only serve as bases). And so, one may not accidentally write

try {
    foo();
}
catch(AssertionException const e) { // oops catching by value
} 

If AssertionException was not abstract, the above could be written. But when it's abstract the compiler will complain at that exception handler, forcing us to catch by reference. And catching by reference is recommended practice.

Marking the member (and class) as final ensures no further derivation is possible. So the problem cannot reappear accidentally when the inheritance hierarchy is changed. Because a programmer that adds another class and again defines defineOnlyInFinalSubclassToPreventSlicing as final will elicit an error from the compiler, on account of this member already being declared final in the base. They will therefore have to remove the implementation from the base class, thus making it abstract again.

It's a bookkeeping system.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    Good link [final specifier](https://en.cppreference.com/w/cpp/language/final) (and good answer) – David C. Rankin Jan 30 '20 at 06:23
  • I don't think that your answer is correct/it is at least a bit confusing. While only virtual functions can be final, they do not have to override _pure_ virtual functions. Hence one could inherit from `struct Base { virtual void foo(){} };` and override `foo` with a final method. One would still be able to instantiate `Base`. The `final` specifier does not in itself prevent that there is only one defined override, but if the programmer only adds a definition to a final override of this method, we are safey since there can't be two final overrides of the same method. – n314159 Jan 30 '20 at 08:40
  • Addedndum: It is also important that the inheritance is final, otherwise one could without problem inherit from the class where the override is final and not touch the override and no compiler is going to say anything. tl;dr: This idiom helps but is no silver bullet. – n314159 Jan 30 '20 at 08:43
  • @n314159 - Never have I claimed only pure virtual functions can be marked final. I have no idea where you got that from. One has to seriously misinterpret my answer to come at that conclusion. I explained the likely bookkeeping mechanism in play here, as evident by the name `defineOnlyInFinalSubclassToPreventSlicing`. – StoryTeller - Unslander Monica Jan 30 '20 at 08:44
  • Maybe I am misinterpreting seriously, but for me it is not clear from your answer that this still needs attention from the programmer. Especially in your first two paragraphs it seems to me like by only adding the final specifier, we ensure that all base classes are abstract, which is not the case. I apologise, if I am the only one that sees it that way, but I wanted to add my observation, in case others would also misinterpret. – n314159 Jan 30 '20 at 08:49
  • @n314159 - Then it seems to you wrong, I have nothing to add. I made no such claim. My first sentence is exposition, the second is my analysis of how the virtual member is **likely** defined to accomplish what it claims to accomplish. Everything else follows from that premise. – StoryTeller - Unslander Monica Jan 30 '20 at 08:51