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

Поделиться
HTML-код
  • Опубликовано: 6 ноя 2024

Комментарии • 60

  • @9uiop
    @9uiop Год назад +13

    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!

  • @xealit
    @xealit 10 месяцев назад +3

    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.

  • @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 Год назад +1

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

    • @weiqiu504
      @weiqiu504 Год назад

      seem like duck typing in python

  • @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.

  • @vegaforce2539
    @vegaforce2539 Месяц назад +1

    I appreciate the effort but this is the opposite of KISS.
    So we replace inheritance with a template class the inherits from a base class to create a wrapper around “any” shape?
    It may only be me but this is confusing as hell. Bring back inheritance then.

  • @IasonNikolas
    @IasonNikolas Год назад +2

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

  • @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.

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

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

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

  • @georganatoly6646
    @georganatoly6646 10 месяцев назад +1

    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

  • @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.

  • @abhinavk0929
    @abhinavk0929 Год назад

    I was waiting for this talk!!!

  • @myusernameislongerth
    @myusernameislongerth Год назад +3

    Hm, Sean Parent did this talk some years ago

  • @vladimirkraus1438
    @vladimirkraus1438 Год назад +2

    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.

  • @miropalmu5588
    @miropalmu5588 Год назад

    Excellent talk!

  • @BalanNarcis
    @BalanNarcis Год назад

    That was fast, good job guys! Great talk!

  • @joeedh
    @joeedh Год назад +3

    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.

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

  • @simonmaracine4721
    @simonmaracine4721 6 месяцев назад

    Great!

  • @nice_sprite5285
    @nice_sprite5285 Год назад

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

  • @shelper
    @shelper Год назад

    is there source code available for this talk?

  • @KeyT3ch
    @KeyT3ch 5 месяцев назад

    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

  • @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.

  • @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!

  • @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.

  • @sqlexp
    @sqlexp Год назад +1

    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 10 месяцев назад +2

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

  • @gregwoolley
    @gregwoolley Год назад

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

  • @maelstrom254
    @maelstrom254 Год назад +3

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

  • @alexeysubbota
    @alexeysubbota Год назад +11

    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:
      ruclips.net/video/4eeESJQk-mw/видео.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 8 месяцев назад

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

    • @usrnm9076
      @usrnm9076 5 месяцев назад

      Skill issue

  • @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.

  • @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 Год назад +2

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

  • @PaulMetalhero
    @PaulMetalhero Год назад

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