Breaking Dependencies - C++ Type Erasure - The Implementation Details - Klaus Iglberger CppCon 2022

Sdílet
Vložit
  • čas přidán 4. 06. 2024
  • cppcon.org/
    ---
    Breaking Dependencies - C++ Type Erasure - The Implementation Details - Klaus Iglberger - CppCon 2022
    github.com/CppCon/CppCon2022
    “If I could go back in time and had the power to change C++, rather than adding virtual function, I would add language support for Type Erasure …” (Eric Niebler, June 19, 2020, Twitter).
    For many developers Type Erasure is superior to inheritance with respect to dependency management. And indeed, it has proven itself to be a powerful design pattern, helping to significantly reduce coupling between software entities. Unfortunately, there is no language support for Type Erasure (yet) and many shy away from the seemingly complex implementation details.
    In this talk I will give advice on the different ways of how to implement Type Erasure. I’ll start with two very simple, ~20 line implementations for owning and non-owning Type Erasure Wrappers. But I’ll also go into detail about different performance optimization strategies, such as the manual implementation of virtual functions and the Small Buffer Optimization (SBO). After this talk, attendees will know everything to realize and utilize Type Erasure in their own code bases.
    ---
    Klaus Iglberger
    Klaus Iglberger is a freelance C++ trainer and consultant. He has finished his PhD in Computer Science in 2010 and since then is focused on large-scale C++ software design. He shares his expertise in popular advanced C++ courses around the world (mainly in Germany, but also in the rest of the EU and the US). Additionally, he is the initiator and lead designer of the Blaze C++ math library (bitbucket.org/blaze-lib/blaze/), one of the organizers of the Munich C++ user group (www.meetup.com/MUCplusplus/), and the organizer of the Back-to-Basics track at CppCon.
    ---
    Videos Filmed & Edited by Bash Films: www.BashFilms.com
    CZcams Channel Managed by Digital Medium Ltd events.digital-medium.co.uk
    #cppcon #programming #cpp
  • Věda a technologie

Komentáře • 59

  • @9uiop
    @9uiop Před rokem +12

    This guy knows a ton about design yet he explains stuff so simply and understandable even novices can a grasp on his topics. Great talk again Klaus!

  • @Niohimself
    @Niohimself Před rokem +17

    The most surprising part of this technique, at least to me, was that the Square doesn't know it's a Shape, or that it can do Shape-ly things, but we do. So that means several people can have a different idea of what a square can do (different ShapeConcept, different number of related free functions) but since all that variation is taken out of the Square, now everyone has the same idea of the "Square itself" so they can send Squares to each other.

    • @joeedh
      @joeedh Před 11 měsíci

      You don't think that's a bad thing? Seems like it would encourage organization balkinization.

    • @weiqiu504
      @weiqiu504 Před 9 měsíci

      seem like duck typing in python

  • @KeyT3ch
    @KeyT3ch Před 13 dny

    Essentially, any struct/class can be a "Shape" as long as it implements the interfaces that Shape requires, and that is amazing, that's what interfaces should just be, without inheritance. This is effectively Go's Interface

  • @Roibarkan
    @Roibarkan Před rokem +4

    43:22 note that std::is_trivially_copyable can (should) be used to verify if copy of the buffer is equivalent to copying the objects. Technically, one could use “if constexpr” in the assignment operator to choose between buffer operation, affordance dispatch or compilation error.Great talk.

  • @BalanNarcis
    @BalanNarcis Před rokem

    That was fast, good job guys! Great talk!

  • @abhinavk0929
    @abhinavk0929 Před rokem

    I was waiting for this talk!!!

  • @xealit
    @xealit Před 5 měsíci +1

    A really brilliant presentation. And Klaus' book "C++ Software Design" is great, by the way. But, after several design patterns, template construction of a concept, with implementations inside hidden friends - how is that KISS? Especially in comparison to the standard and really basic language feature of inheritance? Klaus says "maybe inside it's not that simple, but for the user it's nice" - imagine a user looking at these classes to find out how to extend something (the user has to get to the hidden friends), or just to understand what the interface does. In principle, the user just has to know the design pattern, and preferably everything that goes into it.

  • @IasonNikolas
    @IasonNikolas Před rokem +2

    Adding final in the ShapeModel inheritance might help the compiler to better optimize in some cases.

  • @privateerburrows
    @privateerburrows Před rokem +1

    External polymorphism was the motivation for the good old Visitor Pattern; but this seems far better, with things able to BE more than one thing without incurring multiple-inheritance.

  • @miropalmu5588
    @miropalmu5588 Před rokem

    Excellent talk!

  • @TheCSarmat
    @TheCSarmat Před rokem +2

    Looks like there is a performance issue (time 54:00) related to function "void draw( ShapeConstRef const& shape)". It seems that it would be more efficient code if the function was without const& "void draw( ShapeConstRef shape)" because here we have double-level of pointers dereference. Am I right?

  • @IasonNikolas
    @IasonNikolas Před 10 měsíci

    @CppCon at 21:18 wouldn't be more wise to use the strong exception guarantee version of "Shape& operator=(const Shape& s) { return *this = Shape(s); }" ? I don't quite get why I have to swap the pimpl object instead of the actual Shape.. Isn't it possible for the Shape class to have more state, or only pimpl is allowed in that pattern?

  • @nice_sprite5285
    @nice_sprite5285 Před rokem

    Are the 2500 translates() per shape object, or per shape type?

  • @firejox4982
    @firejox4982 Před rokem

    I found this is useful for unify different IO type, e.g. stdio, socket, pipe, named pipe. These IO types have different ways to construct and storage different information. And that information are not important when we use on reading or writing data. All we care on IO types are reading and writing.

  • @simonmaracine4721
    @simonmaracine4721 Před měsícem

    Great!

  • @shelper
    @shelper Před rokem

    is there source code available for this talk?

  • @vladimirkraus1438
    @vladimirkraus1438 Před rokem +1

    At @12:43, should not there be shape->do_draw() instead of shape->draw() inside drawAllShapes()?

    • @jamesbond0705
      @jamesbond0705 Před rokem

      Yes. The speaker fixed the naming conflicts in his 2021 CppCon talk here, but he forgot to apply that change to this part.

  • @jamesbond0705
    @jamesbond0705 Před rokem +3

    A possible typo report: From slide page 56-68, the term "Concept" should be changed to "ShapeConcept".

    • @MaitreBart
      @MaitreBart Před rokem +1

      I was asking myself the same question: where is this Concept base class coming from?

  • @myusernameislongerth
    @myusernameislongerth Před rokem +3

    Hm, Sean Parent did this talk some years ago

  • @chrisminnoy3637
    @chrisminnoy3637 Před rokem

    This video takes it slow, so good for novices. Be aware that this is a simple example of Type Erasure, don't think by watching this excellent video you know TE.

  • @danielmilyutin9914
    @danielmilyutin9914 Před rokem

    I believe MVD got slower in the end because pointer to function construction and call of function pointer has its cost as you create those temporary ShapeRefConst objects.

    • @Roibarkan
      @Roibarkan Před rokem

      It’s obviously hard to hypothesize without knowing some details of how Klaus implemented SBO+MVD. However, I assume such an implementation won’t have a non-owning ShapeConstRef (like the basic MVD) because SBO implies ownership. I assume the SBO+MVD would be similar to SBO except the buffer inside the shape will directly have (placement new) the actual specific shape-object (no ShapeConcept/ShapeModel), and apart of that buffer every Shape will have a function-pointer (initialized to a lambda) that correctly static_casts the buffer and calls its free-function. My assumption regarding the perf loss is that perhaps the pure MVD solution had a more compact memory usage (4 vectors of the different types of shapes, and a vector of ShapeConstPtr’s that point into them) while the SBO+MVD was a little less compact because 128 bytes are much larger than the actual space needed. Again, great, thought provoking talk!

    • @danielmilyutin9914
      @danielmilyutin9914 Před rokem

      @@Roibarkan I'd like to ask you a little offtopic question. What bugs me is creating temporary view objects takes its toll.
      Imagine matrix library and you want view to row/column and do some operations with it. My tests showed that creation of this temporary object eats too much compared to call some row_fcn, col_fcn.
      Have you experienced something similar? What did you do to fix this?

  • @georganatoly6646
    @georganatoly6646 Před 4 měsíci

    got to be honest, that was pretty rough, trying to find silver linings; it was interesting to see the compiler view through the benchmark results he showed, it appeared like basically the compiler just threw out all that extra stuff, like, if it was obvious the compiler cared one way or another than maybe it could have some value as a potential pattern or anti pattern, but no difference from classical inheritance, that's a bit rough

  • @joeedh
    @joeedh Před 11 měsíci +2

    I've watched this video twice. I just don't find it convincing. There's a lot of talk of how everything is better theoretically, juxtaposed with code that is so much worse. Definitely the kind of thing I'd only consider after extensive profiling.

  • @IasonNikolas
    @IasonNikolas Před rokem

    Did anyone try to use std::shared_ptr to store pimpl object instead on std:unique_ptr? This would remove the need for the clone function inside the concept and any copies will be eliminated! Also the default special member functions will work as expected and there is not need for user defined copy ctor/operator etc. I expect this approach to be more efficient than the simple approach.

    • @alexeysubbota
      @alexeysubbota Před rokem

      But the thing is that we want to have a copy! In your case we just have copy a pointer to ShapeModel but not copy of ShapeModel itself

    • @Tibor0991
      @Tibor0991 Před rokem

      If you're copying, the intention behind is that you want to "fork" the lifetime of the original object; with a shared_ptr, you'd be creating two instances of the same class which point to the same "physical" object (in other words, the same memory area).

    • @GrzesiuG44
      @GrzesiuG44 Před rokem

      This is perfectly valid thing to do when all your operations are const - which you also correctly modeled with the const internal type. There are definetly great use cases for that approach as well: my personal example of choice is a generic id type, for which you can even short-circuit the equality operation and skip virtual call if your pointer points to exactly same pointer.
      I would say this concept of type erasure - although focusing on gaining value semantics - was explored more than 10 years ago in talk "Value Semantics and Concept Based Polymorphism" by Sean Parent (or "Better Code: Runtime Polymorphism" - that one has better quality).

    • @IasonNikolas
      @IasonNikolas Před 10 měsíci

      @@GrzesiuG44 Great talks thank you for pointing them out to me. I was just 10 years late!

  • @rationalcoder
    @rationalcoder Před rokem +7

    I don't like being rude, but what the heck, man. This is basically trying to make up for not having the interface model of Go in C++, but it's almost never worth doing. I _have_ found something like this useful in situations like defining something similar to MetaTypes in the Qt framework (but in my own code) and even that was just to be able to define components of external types that have no knowledge that they are components. Also, boiling types down to void* and size/alignment or whatever internally _is_ in fact type erasure. Anytime you have types in, no types internally, and types out, you can generally call that type erasure.

  • @pawello87
    @pawello87 Před rokem +2

    In classic OOP I can do:
    auto my_circle = make_shared(2.5f);
    shapes->add_shape(my_circle); // add_shape accepts base class ptr, ex. shape*
    my_circle->set_radius(1.0f);
    I can store that pointer and modify this concrete circle any time i want
    In this type-erasure implementation:
    auto my_circle = circle{2.5f};
    shapes->add_shape(my_circle); // my_circle is moved-out
    my_circle.set_radius(1.0f); // use-after-move!
    Well, I'm losing any possibility to modify and even access that circle instance.
    Conclusion: I prefer to leave lifetime responsibility to the user than give him a fake simplicity by the cost of limited access to his type.
    I see a lot more advantages in type-erasure based on shared_ptr.

    • @blacklion79
      @blacklion79 Před rokem +2

      Classic OOP doesn't care about value types and «loves» mutability. Modern OOP cares about value types and tries to avoid mutability, because non-concurrent programming becomes less and less relevant and concurrent mutability is very error-prone and non-scalable.

    • @pawello87
      @pawello87 Před rokem

      @@blacklion79 Ok, but you lose access to *any*, even non-mutating method.

    • @junekeyjeon1124
      @junekeyjeon1124 Před rokem +2

      Not quite. my_circle is copied, not moved. The std::move you see in the constructor is for moving out of the parameter, not the argument passed. The argument will be either copied or moved into the parameter depending on the value category of it, which is determined by how you call the constructor. In your example you passed my_circle which is an lvalue, so it gets copied into the parameter (which in turn is moved into the newly created shape). On the other hand if you wrap your my_circle with std::move, then it will get moved into the parameter (which in turn is moved again into the newly created shape). There is no use-after-move in your example.

  • @gregwoolley
    @gregwoolley Před rokem

    Nice talk, but the loud sibilance made it painful to listen to. Need better microphone to tone down those super-loud 'ssssssss'.

  • @christiandaley120
    @christiandaley120 Před rokem

    37:20 I believe that std::aligned_storage_t would be better suited for this rather than std::array

    • @idanaharoni8401
      @idanaharoni8401 Před rokem +1

      aligned_storage is deprecated in C++ 23

    • @antagonista8122
      @antagonista8122 Před rokem

      Since C++ introduced alignas it's not necessary to use types that depends on compiler intrinsics (e.g. aligned_storage_t).
      Both alignas array and aligned_storage_t have equally non-intutive API (user has to explicitely use reinterpret_cast and placement new) so it doesn't really matter.
      + aligned_storage_t is deprecated since C++23 due to terrible API - ^ + users often incorrectly use aligned_storage type directly instead of aligned_storage::type/aligned_storage_t alias as they supposed to do.

  • @alexeysubbota
    @alexeysubbota Před rokem +9

    I didn't understand what this talk is about. There were many classes in which it is easy to get confused. It was hard to keep them all in mind. Maybe a class diagram could've helped understanding. I was tied after watching a half of the presentation. Without an example of problem I didn't understand what the problem he was solving... After viewing, there was a feeling of overcomplication

    • @alexey104
      @alexey104 Před rokem +1

      The first part of this talk describes the problem with the traditional approach using inheritance:
      czcams.com/video/4eeESJQk-mw/video.html

    • @guenterscherling9069
      @guenterscherling9069 Před rokem +2

      Actually, C++ including templates does not need all this C++11/C++17 etc. stuff.
      The consultants need it.
      C++ is going to be destroyed.

    • @Peregringlk
      @Peregringlk Před 3 měsíci

      Inheritance is a bad design pattern. This talk is about getting dynamic polymorphism WITHOUT inheritance exposed in the interface.

    • @usrnm9076
      @usrnm9076 Před 2 dny

      Skill issue

  • @sqlexp
    @sqlexp Před 7 měsíci

    This presentation doesn't give C++ a good reputation. Unless you are stuck with using C++14 or earlier, you should just use std::variant if you can provide the list of shape types ahead of time. Just replace std::unique_ptr or the aligned array with std::variant in Shape class, and use a switch statement to dispatch the call to draw/serialize. There is no need for ShapeConcept and ShapeModel. There is no additional allocation from the heap, no placement new, and no virtual function call.

    • @isodoublet
      @isodoublet Před 5 měsíci +2

      He addressed this exact point in the Q&A. They do different things.

  • @PaulMetalhero
    @PaulMetalhero Před rokem

    Generally nice design, but that first optimization is nasty. Manually calling a destructor? No way

  • @maelstrom254
    @maelstrom254 Před rokem +2

    C++ is dead to me, if such trivial things require so much effort 😢

  • @treyquattro
    @treyquattro Před rokem +7

    I believe type-erased C++ is called C...

    • @chrisminnoy3637
      @chrisminnoy3637 Před rokem +1

      Very far from it. C should be labeled deprecated.

    • @maelstrom254
      @maelstrom254 Před rokem +1

      @@chrisminnoy3637 rather C++ is deprecated, if such trivial things as Shape require such insane amount of effort to implement 🤯

    • @chrisminnoy3637
      @chrisminnoy3637 Před rokem

      @@maelstrom254 sure, whatever you say bro, if you want to stick to 8 bit processors and only simple logic to run.

    • @Adowrath
      @Adowrath Před rokem +1

      @@maelstrom254 What do you mean "such trivial things as Shape"? Is "an extensible range of types that all share an equally extensible set of operations such as drawing and serialization" just trivial to you? Do you need to create new such hierarchies/sets of types every day in your programming language?