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

Поділитися
Вставка

КОМЕНТАРІ • 59

  • @9uiop
    @9uiop Рік тому +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 Рік тому +18

    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 Рік тому

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

    • @weiqiu504
      @weiqiu504 Рік тому

      seem like duck typing in python

  • @xealit
    @xealit 8 місяців тому +2

    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.

  • @Roibarkan
    @Roibarkan Рік тому +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.

  • @privateerburrows
    @privateerburrows Рік тому +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.

  • @KeyT3ch
    @KeyT3ch 3 місяці тому

    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

  • @IasonNikolas
    @IasonNikolas Рік тому +2

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

  • @jamesbond0705
    @jamesbond0705 Рік тому +3

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

    • @MaitreBart
      @MaitreBart Рік тому +1

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

  • @firejox4982
    @firejox4982 Рік тому

    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.

  • @BalanNarcis
    @BalanNarcis Рік тому

    That was fast, good job guys! Great talk!

  • @chrisminnoy3637
    @chrisminnoy3637 Рік тому

    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.

  • @myusernameislongerth
    @myusernameislongerth Рік тому +3

    Hm, Sean Parent did this talk some years ago

  • @TheCSarmat
    @TheCSarmat Рік тому +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?

  • @abhinavk0929
    @abhinavk0929 Рік тому

    I was waiting for this talk!!!

  • @georganatoly6646
    @georganatoly6646 8 місяців тому

    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

  • @IasonNikolas
    @IasonNikolas Рік тому

    @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?

  • @miropalmu5588
    @miropalmu5588 Рік тому

    Excellent talk!

  • @simonmaracine4721
    @simonmaracine4721 4 місяці тому

    Great!

  • @danielmilyutin9914
    @danielmilyutin9914 Рік тому

    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 Рік тому

      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 Рік тому

      @@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?

  • @vladimirkraus1438
    @vladimirkraus1438 Рік тому +1

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

    • @jamesbond0705
      @jamesbond0705 Рік тому

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

  • @nice_sprite5285
    @nice_sprite5285 Рік тому

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

  • @joeedh
    @joeedh Рік тому +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.

  • @shelper
    @shelper Рік тому

    is there source code available for this talk?

  • @IasonNikolas
    @IasonNikolas Рік тому +1

    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 Рік тому

      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 Рік тому

      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 Рік тому

      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 Рік тому

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

  • @rationalcoder
    @rationalcoder Рік тому +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 Рік тому +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 Рік тому +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 Рік тому

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

    • @junekeyjeon1124
      @junekeyjeon1124 Рік тому +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.

  • @alexeysubbota
    @alexeysubbota Рік тому +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 Рік тому +1

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

    • @guenterscherling9069
      @guenterscherling9069 Рік тому +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 6 місяців тому

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

    • @usrnm9076
      @usrnm9076 3 місяці тому

      Skill issue

  • @gregwoolley
    @gregwoolley Рік тому

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

  • @christiandaley120
    @christiandaley120 Рік тому

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

    • @idanaharoni8401
      @idanaharoni8401 Рік тому +1

      aligned_storage is deprecated in C++ 23

    • @antagonista8122
      @antagonista8122 Рік тому

      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.

  • @sqlexp
    @sqlexp 10 місяців тому

    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 8 місяців тому +2

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

  • @treyquattro
    @treyquattro Рік тому +7

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

    • @chrisminnoy3637
      @chrisminnoy3637 Рік тому +1

      Very far from it. C should be labeled deprecated.

    • @maelstrom254
      @maelstrom254 Рік тому +1

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

    • @chrisminnoy3637
      @chrisminnoy3637 Рік тому

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

    • @Adowrath
      @Adowrath Рік тому +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?

  • @maelstrom254
    @maelstrom254 Рік тому +2

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

  • @PaulMetalhero
    @PaulMetalhero Рік тому

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