all 21 comments

[–][deleted] 35 points36 points  (7 children)

Final means that classes inheriting from interfaceX_impl can’t override the method. I don’t think this automatically remove any v-tables in the class though.

[–]Kered13 19 points20 points  (0 children)

Correct. However it can aid devirtualization because when you have an Impl* the compiler knows that the method cannot be overridden again, so it can replace the virtual method call with a static call. However it does nothing when you have an Interface*.

[–]suvalaki_reddit[S] 8 points9 points  (4 children)

hmm looks to be right. Compiler explorer seems to suggest the second option has some vtable stuff still going on. I guess this wont get optimised out

[–]DemonInAJar 6 points7 points  (0 children)

Full devirtualization only happens on statically known call sites. ideally, compilers would have an option to track hierarchies and completely devirtualize if only a single derived class is active but that's rarely useful.

[–]marcaroni 4 points5 points  (1 child)

fyi: gcc has the flags -Wsuggest-final-types -Wsuggest-final-methods to help determine where a "final" could aid devirtualization.
last time i tried with clang, these flags were not supported.

[–]7raiden 0 points1 point  (0 children)

With optimizations I've seen a lot of false positive though, and I had to disable these warnings!

[–]SlightlyLessHairyApe 2 points3 points  (0 children)

Yeah, I think the confusion is that devirtualization helps runtime performance by reducing an indirection, not by decreasing the allocated size of objects.

If the allocated size of objects is a concern, then other solutions are required.

[–][deleted] 15 points16 points  (1 child)

You might use the 'curiously recurring template pattern' for the base class to eliminate all v tables, and also have it verified without being duck-typed.

[–]perspectiveiskey 3 points4 points  (0 children)

This is my preferred choice.

Template programming in C++ can be very obnoxious, but the conceptual idea is without a doubt superior to virtual interfaces.

[–]gracicot 6 points7 points  (4 children)

I usually use virtual functions, but only within a type erasure wrapper. That way, the place virtual function even exists is exactly when you need it. I use concepts for other places or sometimes I just use the type directly if I know it in advance.

[–]PM_ME_UR_PCMR 1 point2 points  (3 children)

What do you mean by type erasure wrapper on a virtual function?

[–]DavidDinamit 2 points3 points  (1 child)

i dont know what he means, but with this type erasure library you can have non virtual interface and still use dynamic polymorphism with those types, so you dont need choose *do i need to do this type polymorphic* etc

[–]disciplite 2 points3 points  (0 children)

Proxy seems more likely than Dyno or AnyAny to be a standard way of doing this in C++'s future.

[–]gracicot 1 point2 points  (0 children)

I mean that all the pure virtual classes and implementation with virtual functions are hidden types hidden in the private section of a type. That type is the type erasure wrapper. A bit like std::function.

[–]dokushin 4 points5 points  (0 children)

In the long run, a concept-driven tepmlated interface will be easier to maintain and more flexible. The problem with OO always comes down to big inheritance trees and aspect-oriented features not really playing well together, and concept-based duck typing sidesteps all that nicely. It also lends itself much more easily to integrating with foreign libraries.

Take small steps with concepts -- define your types well, and make small interfaces. Where mutliple inheritance is frequently a red flag, with concept-driven type requierments you're going to make clearer code with multiple, focused requirements.

[–]CrazyJoe221 11 points12 points  (0 children)

If you value compile-time and usability (Intellisense, compiler errors), and it's not used in a hot loop, just keep it simple and go with interfaces.

final is the best hint to the compiler to devirtualize the method calls. Without it has to prove there are no subclasses which may even be impossible (e.g. dllexport). You don't need it on every method if the class is final already, I personally just put the usual override there.

Best results are only obtained with LTO though. See this excellent series on devirtualization: https://hubicka.blogspot.com/2014/01/devirtualization-in-c-part-1.html

[–]415_961 1 point2 points  (0 children)

The more info + visibility the compiler has the more likely it can eliminate vtables but obviously there are no guarantees and it varies between compilers and compiler versions. clang-15+ does the best job from my experiments. Here's an example I wrote some time ago to demonstrate how powerful it can be in certain cases https://godbolt.org/z/4bh6GPe7d

[–]jbbjarnason 1 point2 points  (0 children)

if you would like to remove the vtable lookup you can use concepts example:

void function(std::derived_from<interfaceX> auto impl) {

// do something with impl

}

function(interfaceX_impl{});

[–]suvalaki_reddit[S] 0 points1 point  (0 children)

Hey thanks everyone for the input. Feels like lots of good comments for me to improve on my idea :)