Using Modern C++ to Eliminate Virtual Functions - Jonathan Gopel - CppCon 2022

แชร์
ฝัง

ความคิดเห็น • 98

  • @m3ttwur5t
    @m3ttwur5t ปีที่แล้ว +274

    Thanks, Gigachad.

    • @mister-ace
      @mister-ace ปีที่แล้ว +12

      lmao

    • @yezariaelll
      @yezariaelll ปีที่แล้ว +15

      Damn you...hard to unsee. Cool talk tho, good presenter. :)

  • @embeddor3023
    @embeddor3023 ปีที่แล้ว +86

    A gigachad talk :)

  • @williamvannoordt9545
    @williamvannoordt9545 ปีที่แล้ว +12

    Interestingly enough I implemented something almost identical to this (although not quite as clean) in my codebase before discovering this talk. I can vouch that, for my application (massively parallel fluid simulation), there were performance benefits over std::variant that made it worth it, and the implementation is nothing crazy.

  • @treyquattro
    @treyquattro ปีที่แล้ว +18

    great talk by a knowledgeable speaker. I found the colors on the slides difficult to make out however, until I was blowing my retinas out with the white background

    • @jonathangopel8967
      @jonathangopel8967 ปีที่แล้ว +14

      Thanks for your kind words!
      My apologies for the slides - I had them on a dark background originally. I had technical issues day-of, and as a result I didn't realize that the projector in the room didn't like my dark color scheme until the last minute. I switched the color scheme about 15 minutes before the start of the presentation and so I didn't have any time to fine tune it. A dark background should be available in the CppCon GitHub repo.

  • @Bolpat
    @Bolpat ปีที่แล้ว +10

    If you have tuple and need stable iteration, you can use tuple where the INT in each pair represents the vector to look into next (this type INT needs to hold the number of vectors, i.e. needs not be size_t; for < 256 vectors, std::uint8_t is fine). The manager must keep track of the last vector (store 1 INT each) that was inserted into, so that its last value’s INT component can be updated when another element is inserted. It does not have to track the whole element, because insertion order in a vector _is_ stable. When iterating, one must keep track of the next element in each vector.

    • @DFsdf3443d
      @DFsdf3443d 10 หลายเดือนก่อน

      very interesting have you made any benchmarks with this technique? Obviously it adds a bit of runtime overhead when iterating and inserting but it should still be better than the pointer indirection from the polymorphic approach, right?

    • @Phroggster
      @Phroggster 10 หลายเดือนก่อน

      I would think that you'd probably have to use a tuple to maintain logical ordering. The INT keeps the same meaning as OP's, while the size_t maintains the next index into the indirected vector. Otherwise, all one would have is knowledge of which vector is next in the logical ordering, but not which element.
      The INT type would also need be signed, as it's trivially conceivable that you'd have to iterate into a prior vector of the outer tuple.

  • @bunpasi
    @bunpasi ปีที่แล้ว +9

    Thanks for the talk. Very interesting.
    I would only use this when it has a significant impact on performance in an area where it matters. It looks quite convoluted. I'd pick it over SFINAE every day though 😝.

    • @etherstrip
      @etherstrip ปีที่แล้ว +1

      It is not as convoluted as it looks. As is common with those types of talks, in the effort to show all that can be done, the talk gives the feeling of something overwhelming. Applied to real code however, you rarely need all of that and certainly never all at the same time.
      That being said, it is still true it's just a tool in the toolbox, not a silver bullet.

    • @notexpando3179
      @notexpando3179 ปีที่แล้ว +2

      whats wrong with SFINAE

    • @jonathangopel8967
      @jonathangopel8967 ปีที่แล้ว

      @@etherstrip I couldn't agree more. With just a little practice, this approach to writing code is not very complex - you just have to learn how to think about things through a different lens.

    • @1wisestein
      @1wisestein หลายเดือนก่อน

      It’s less convoluted than using virtual functions

  • @justdoityourself7134
    @justdoityourself7134 ปีที่แล้ว +5

    Great presentation.

  • @benjamin_fdw
    @benjamin_fdw ปีที่แล้ว +13

    How did we ended up making the syntax even more complex...

    • @lunakid12
      @lunakid12 5 หลายเดือนก่อน +3

      Because his goal was not to optimize syntax but run-time cost.

  • @fjrg76
    @fjrg76 ปีที่แล้ว +5

    Meantime while CPP 20 becomes mainstream (10 years or so), we can use the CRTP (Curiously Recurring Template Pattern ) pattern that has been around for a long to create static interfaces, unless CPP 20 is covering something else that CRTP can not.

    • @chanaaron2533
      @chanaaron2533 ปีที่แล้ว +2

      I just wonder how do you implement a heterogeneous container to store CRTP objects? Would you mind sharing some tips?

    • @Antagon666
      @Antagon666 ปีที่แล้ว +2

      @@chanaaron2533 You can't. You will end up having to create virtual base class, inherit the CRTP class and other classes from that. Tadaaa and you have just invented polymorphism with extra steps.

    • @FalcoGirgis
      @FalcoGirgis 10 หลายเดือนก่อน +2

      Heh. Fellow CRTP-ers. You guys seen C++23's "deducing this?" You gotta admit, that's at least nicer for CRTP... Pretty excited for that one.

  • @stavb9400
    @stavb9400 ปีที่แล้ว +2

    That tuple of vectors good idea but need to see in practice

  • @broken_abi6973
    @broken_abi6973 ปีที่แล้ว +7

    For me, the only good use cases for virtual functions are: (1) having a container that can hold multiple implementations of the same base class and (2) decoupling implementations to have more unit-testable code. Variants or tuples of containers are sometimes a good alternative for case 1. However, there is no really good replacement for case 2. that does not involve some level of indirection.
    Also, I should remind people here that the main cost of virtual functions is the fact that they are hard to inline. Their overhead is comparable to normal non-inlined functions, and usually, people mistake the overhead of virtual functions for the overhead incurred by the fact that they allocated their polymorphic objects on the heap.

    • @seannull
      @seannull ปีที่แล้ว

      Totally agree. What would you perfer for real interface? Ftadle?

    • @iliasalaur
      @iliasalaur 8 หลายเดือนก่อน

      For the first use case, you should consider using a type-erased solution. (check Klaus Iglberger's talk on type erasure)

  • @Kolsha
    @Kolsha ปีที่แล้ว +16

    Great talk!
    Why do you use
    auto update() -> void {
    and not
    void update() {
    ?

    • @mek101whatif7
      @mek101whatif7 ปีที่แล้ว +10

      Style consistence

    • @abhinavk0929
      @abhinavk0929 ปีที่แล้ว +1

      That's just a style choice I guess

    • @NeatuOvidiu
      @NeatuOvidiu ปีที่แล้ว

      Usually a return type name can get complicatedly long so it makes it easier to spot the function name.
      I think it's a nice idea to write like this, might be even nicer if C++ would allow to skip the auto part as well:
      function() -> (return_type)

    • @nextlifeonearth
      @nextlifeonearth ปีที่แล้ว +4

      For consistency that's a pretty good idea to do. The advantage of auto name() -> type is that you can use the parameter to define the return type. Example:
      auto myFunction(auto parameter) -> decltype(parameter.someFunction()) ...
      Imagine that someFunction() may have a different type depending on the type of parameter.
      You can't do that if you put the return type before the function name.
      It's also consistent with lambdas. You can specify the return type in lambdas, contrary to some people's beliefs, but you need to do it with the arrow;
      const auto myLambda = [] () -> SomeType { return {1,2,3}; };
      So it has a lot of advantages.

  • @treyquattro
    @treyquattro ปีที่แล้ว +17

    it would have been good to have some data showing performance differences using this method. While I think the subject and talk is very interesting, I remain skeptical about the performance gains.

    • @tissuepaper9962
      @tissuepaper9962 ปีที่แล้ว +5

      He literally mentioned another talk that already happened that shows hard numbers for the performance benefits, while explaining why he didn't show any numbers.

    • @jonathangopel8967
      @jonathangopel8967 ปีที่แล้ว +23

      Just before I gave my talk, Ivica Bogosavljevic gave a talk on performance. It's live on TH-cam already - check it out!

  • @chrisminnoy3637
    @chrisminnoy3637 ปีที่แล้ว +13

    Virtual function performance is often dependent on the compiler to determine if a call is really virtual or not. But on mature CPUs you also have branch prediction, so a virtual function will have zero performance overhead if and only if the CPU can perfectly predict the destination address. On tiny embedded cpus that might be an issue. I agree that a programmer always have to consider if a virtual function is needed or not.

    • @T0m1s
      @T0m1s ปีที่แล้ว +4

      Maybe, maybe not? When discussing runtime polymorphism, the more important scenario is "many objects in a container, processed sequentially". This is what OOP proponents commonly cite as an advantage of their favourite paradigm ("I can store a vector of Employee objects and I don't need to care about the internal implementation details"). In this scenario it's very unlikely that the compiler will be able to devirtualize, because the container is usually populated at runtime. CPU branch prediction can only do so much to help, you'll still likely get a performance penalty. Another factor is the vptr storage, which means that, at the very least, you can store fewer objects in a cache line because each object now occupies an extra 8 bytes.

    • @llothar68
      @llothar68 ปีที่แล้ว

      You have a triple indirection. From the this object, to the vmt table to the code. Tell me which CPU can optimize this away? One better solution was Eiffel, not only gave it perfect multiple inheritance but it was also faster for most cases. THey generated switch statement trampolines on a per function base, not a VMT for the whole class.

    • @nextlifeonearth
      @nextlifeonearth ปีที่แล้ว +7

      On pretty much any modern CPU a virtual function call is a guaranteed branch miss. Make sure they're not on a hot spot in the code at the very least.

  • @oschonrock
    @oschonrock ปีที่แล้ว +2

    Very good!

  • @janhruby9379
    @janhruby9379 ปีที่แล้ว +20

    Good presentation on concepts. BUT you don't need unique_ptr / shared_ptr to call virtual functions. They can be stack allocated a passed around as references. In that case with a final keyword it compiler will most likely devirtualize and you write simpler code. Also why would you define all other constructors/operators if you pass-around the type as a pointer/reference.
    I would say :
    1. You want to represent a value. Use variant for polymorphism when you need it.
    2. You want to represent a API to achieve something go for virtuals ;)
    I you design the API well the virtual call cost is negligible.

    • @willwu7353
      @willwu7353 ปีที่แล้ว +3

      Yes true but then they're polymorphic pointers with the guise of a reference.. This can lead to confusion - usually when I see something taking const Foo& my mind doesn't immediately think ooh this thing's actually polymorphically derived from Bar and implements 3 of its virtual functions. And if they're stack allocated marked final in a visible translation unit the class layout would be resolved and even inlined anyway then there isnt really anything to devirtualize. I think virtual functions are useful for realizing APIs across different sometimes tricky boundaries (extern, PIMPL, dlsym/dyld type stuff) where theres only so much a compiler can do, at that point yah I agree.. you feel way less bad about a bunch of virtual calls because guess what the method u just shimmed i.e the guy next door has 20 more of his own on the callstack!

    • @T0m1s
      @T0m1s ปีที่แล้ว

      "They can be stack allocated a passed around as references" - I'd argue the most common scenario is allocating a container of heterogeneous objects at runtime (e.g. read from a database and use a factory to populate a vector); in this case I guess it's obvious that the compiler can't devirtualize. For APIs, I'd be careful about using virtuals because you could have methods that are called very frequently (a common example are naive graphics APIs that add a shape to the canvas, or objects that have a Render method). I guess the more important question is why use virtual at all.

  • @sj71252
    @sj71252 ปีที่แล้ว +7

    - It is very convoluted and therefore error-prone
    - It requires lot of boiler-plate
    - You need to know all the types at compile time to create an instance e.g., "Foo". Virtual inheritance allows run-time selection e.g., Base *base = Base::New("Derived")

    • @dexio85
      @dexio85 10 หลายเดือนก่อน +4

      Yup, this thole presentation is so far from real world application that it's frightening such people take active part in language development. Design by comitee at it's worst.

    • @DFsdf3443d
      @DFsdf3443d 10 หลายเดือนก่อน +2

      yeah and in python you dont even have to know the type to create an instance. See i can do your level of criticism too.
      Point is that convenience comes at a cost. This talk is about a way to achieve polymorphic behavior without the cost in the first place. Yes its a bit more convoluted and not as convenient as you have to know every type at compile time, but for certain applications its more performant than using polymorphism.

    • @bayern1806
      @bayern1806 7 หลายเดือนก่อน

      @@dexio85 I did not even watch the video, I knew it was trash, Was looking for a comment to confirm the assumption.

    • @bayern1806
      @bayern1806 7 หลายเดือนก่อน

      @@DFsdf3443d You can achieve C++ behavior with C, so lets switch to C? What kind of argument is that? Please prove your point for more performance. Assembler is also more performant than C++, yet nobody could ever prove it in a real world application, because the compiler is better at generating assember. take an 100k loc application and rewrite in in assembler, and run the tests.

  • @yrds96
    @yrds96 18 วันที่ผ่านมา

    Can you use polymorphism without virtual?
    Can you use concept to achieve this?
    Can they serve the same purpose?
    Can you feel, Can you feeeeeel myyyy heaaaart?

  • @DedmenMiller
    @DedmenMiller ปีที่แล้ว +2

    30:00 downsides is missing a point.
    Need to know fixed list of types at the time where you place the container that will store your things.
    That means if a 3rd party wants to extend your code in the future, they have no option other than modifying/patching your code.
    Where-as with virtual, it wouldn't matter as you just insert a pointer to the base class.

    • @dexio85
      @dexio85 10 หลายเดือนก่อน +1

      Also it's missing any real performance data. In real world code the cost of virtual function is trival compared to other operations.

    • @DFsdf3443d
      @DFsdf3443d 10 หลายเดือนก่อน

      @@dexio85 still it is a cost, that can be avoided. Also im not sure i believe you when you say its a small cost, in certain scenarios it can be come quite costly.

    • @DFsdf3443d
      @DFsdf3443d 10 หลายเดือนก่อน +1

      he addressed this at he start of the talk, he is not saying to never use the virtual keyword again, he is simply showing a more performant way to achieve polymorphic behavior that may be better suitable in certain applications. Obviously a third party polymorphic library interface isn't a suitable application in this case.

  • @KillerMZE
    @KillerMZE ปีที่แล้ว +8

    The overusage of "foo" makes this hard to follow

  • @artemp.2122
    @artemp.2122 ปีที่แล้ว +1

    in cases this presentation could substitute virtual, simple switch or even if will do the same i guess

  • @bearwolffish
    @bearwolffish 5 หลายเดือนก่อน +1

    Let the man cook!

  • @stanbarvinsky1011
    @stanbarvinsky1011 5 หลายเดือนก่อน

    Great talk! Type Erasure is not a void* tho

  • @Sajal89
    @Sajal89 10 หลายเดือนก่อน

    why after 2 slides they have to make it red black and arrows pointing here there, why not explain the concept in simple words?

  • @player-eric
    @player-eric ปีที่แล้ว

    Could you please provide accurate subtitles for this video?

  • @lancecrimm
    @lancecrimm ปีที่แล้ว

    Awesome sauce so proud of you!

  • @meowsqueak
    @meowsqueak ปีที่แล้ว

    In the final code, is this a good example of using Concepts to avoid the use of CRTP for implementing static polymorphism (to enforce that concrete types implement a specific interface)?

    • @player-eric
      @player-eric ปีที่แล้ว

      Could you please add the timestamp?

  • @Ptr-NG
    @Ptr-NG 8 หลายเดือนก่อน

    cpp people are working so hard to improve the language so much so, it is getting overwhelming ... "mental exercise" :)

  • @soniakolasinska3850
    @soniakolasinska3850 ปีที่แล้ว +1

    I 100% agree with the "bold statement" - you don't need to use virtual, you don't need to use smart pointers. I used that methodology in my c++20 order book project, which I talk about on my channel

  • @BertLaverman
    @BertLaverman ปีที่แล้ว +4

    Ok, I came to this because my daytime job is mostly Java, where every non-static method is virtual, and I was trying to see how this would work out. I have to admit our class hierarchies potentially grow deep and wide. It is a lot of behavioral inheritance with abstract methods as placeholders for different variations of behavior depending on where in the hierarchy you are. Somehow I fail to make the connection with this talk. I am guessing your usage of class hierarchies is simply to achieve different goals than we do. So far you seem to say that "if you don't need virtual you can get quite far without it" as well as "concepts can do neat things." That last one is definitely a takeaway for me...

    • @jonathangopel8967
      @jonathangopel8967 ปีที่แล้ว +6

      It sounds to me like your primary usage of virtual is primarily the first case on slide 2-1 - binding a specific interface. If that's correct, your takeaway that concepts can serve a very similar function is spot on!

  • @christer8964
    @christer8964 ปีที่แล้ว

    Nice option to virtual.

  • @tahitinguyen4822
    @tahitinguyen4822 9 หลายเดือนก่อน +1

    This guy's name is Chad, not Jonathan

  • @player-eric
    @player-eric ปีที่แล้ว

    Is the source code of this video released?

    • @bayern1806
      @bayern1806 7 หลายเดือนก่อน

      No, since no compiler can build it 🤣

  • @petermuller608
    @petermuller608 3 หลายเดือนก่อน

    Aren't his interfaces missing a virtual destructor?
    Edit: should have listened for five more seconds before posting xD

  • @spirothegamer
    @spirothegamer 7 หลายเดือนก่อน

    Dude can pick up a career in modeling if he ever gets bored with coding😁

  • @mohammadmahdifarnia5358
    @mohammadmahdifarnia5358 ปีที่แล้ว

    I learned too much on concepts & the main subject. Thank🙏
    If anyone who read this comment knows a reference to learn more about cpp concepts please let me know… (video or blog …)

  • @WilhelmDrake
    @WilhelmDrake ปีที่แล้ว +2

    Constructive criticism:
    1) Use better code font and size
    2) Don't clear your throat constantly in the microphone.
    Otherwise thanks for the talk, very interesting.

  • @pilotnet1
    @pilotnet1 6 หลายเดือนก่อน

    Please, don't use dark theme

  • @heyquantboy
    @heyquantboy ปีที่แล้ว +2

    The sample code is completely unreadable. Why use such small fonts?
    There's vast areas of white space to the left, right top and bottom. The margins are white, and the code is in dark theme. Unreadable.
    What's the presenter thinking?

  • @WndSks
    @WndSks ปีที่แล้ว +2

    Code font is too small and so much wasted space around it, unwatchable :(

  • @moderncpp
    @moderncpp ปีที่แล้ว +4

    Great talk! A little peace of advice: Try to eliminate the ahs and ahms, otherwise great content.

    • @jonathangopel8967
      @jonathangopel8967 ปีที่แล้ว +2

      Thanks for watching and thanks for the advice!

  • @niklkelbon3662
    @niklkelbon3662 ปีที่แล้ว +2

    std::variant is not enough, there are many use cases when it is inapplicable or ineffective such as huge difference between sizeof's, required incomplete type (variant needs to know all types when created while type erasure can be used for recursive types etc)
    For example:
    struct expression {
    expression* a;
    expression* b;
    };
    Its impossible to do with variant.
    And yes, virutal functions is bad, but solutions exist already...(cant post links here =)))

    • @nitori_kawashiro
      @nitori_kawashiro ปีที่แล้ว

      the variant will not be recursing much like how a virtual class can't inherit itself
      if you are referring to how a class that is included in the variant can contain the variant, that is not possible with OOP and inheritance either, you do not store values of base classes, you store references to them; that is to say that a class satisfying the variant's concept should not be storing the variant but a pointer to it (and all pointers are sizeof(void*) so there's no recursion)

    • @yangwei6761
      @yangwei6761 ปีที่แล้ว +1

      Yes, let me add that you must know that the type is OK for the framework developer, but it is really really bad for the framework user because the framework developer cannot know the type to be defined by the user in advance, which is very disgusting.

    • @yangwei6761
      @yangwei6761 ปีที่แล้ว

      so, leaving the dynamic interface instead of static is still a good choice

    • @llothar68
      @llothar68 ปีที่แล้ว

      When i wrote a compile. Storing function pointer with the expression was even better.

  • @moshegramovsky
    @moshegramovsky ปีที่แล้ว +3

    Unfortunately I couldn't read the text on the dark background so I stopped watching.

  • @matthijshebly
    @matthijshebly 6 หลายเดือนก่อน +2

    The constant "uh" and coughs etc make this unlistenable

  • @paulfunigga
    @paulfunigga ปีที่แล้ว +11

    Looking at this code makes me want to say, "C++ people, are you freaking kidding me?". C++ has become an overly complicated mess of a language, it has to go.

    • @dat_21
      @dat_21 ปีที่แล้ว +3

      Don't worry, almost nobody uses template salad in real world code. C++ is expressive enough even without use of these "modern" features.
      And to be really honest, language itself is very stagnant. Whole c++ philosophy these days is "we do that in std". And that led us to atrocities such as std::initializer_list. Or very non-obvious template rules.
      Template rules are so stupid, that sometimes I feel like it's simpler to write a code generator in python. Only in most trivial cases they work as intended.

    • @markedel1009
      @markedel1009 ปีที่แล้ว +1

      I just kept thinking "wow, I'm glad I'm not using C++ any more".

    • @scotthinton4610
      @scotthinton4610 ปีที่แล้ว +1

      True. Modern C++ is the sort of thing that makes me think "Rustaceans" might be on to something...

    • @dnkreative
      @dnkreative ปีที่แล้ว +3

      It's a parody to programming. I wonder if these people develop anything useful but examples

    • @itellyouforfree7238
      @itellyouforfree7238 ปีที่แล้ว +3

      Ok, so, name another language where a type can be generic over a *list* of other types. I know one, but you go first

  • @adam4813
    @adam4813 ปีที่แล้ว +2

    For lists that don't need to be created as instances, I've found the multiton pattern to be very similar in usage, and a common base class is not needed en.m.wikipedia.org/wiki/Multiton_pattern