why does inheritance suck?

  Рет қаралды 201,581

Low Level Learning

Low Level Learning

Күн бұрын

You've probably heard this a few times when talking to your fellow programmer friends. "Gee Billy C++ polymorphism sure is slow, I hope Sally doesn't know that I use it!" But why is it so bad? In this video, we'll do a deep dive on what C++ Polymorphism is, what "virtual" does under the hood, and ultimately why it is SUCH a performance hit compared to languages like C and Rust.
🏫 COURSES 🏫 Check out my new courses at lowlevel.academy
🙌 SUPPORT THE CHANNEL 🙌 Become a Low Level Associate and support the channel at / lowlevellearning
How Does Return Work? • do you know how "retur...
🔥🔥🔥 SOCIALS 🔥🔥🔥
Low Level Merch!: lowlevel.store/
Follow me on Twitter: / lowleveltweets
Follow me on Twitch: / lowlevellearning
Join me on Discord!: / discord

Пікірлер: 775
@LowLevelLearning
@LowLevelLearning 10 ай бұрын
IM GOING LIVE TODAY AT 12PM EST TO REVIEW MY CHATS CODE: kzfaq.info-A6Ar4u_Teg
@kuhluhOG
@kuhluhOG 10 ай бұрын
I think you should add that *runtime* polymorphism was discussed in the video. C++ can also do compile time polymorphism (which doesn't have a runtime performance overhead).
@EmilPrivate
@EmilPrivate 10 ай бұрын
@@kuhluhOG but it has other trade-offs
@kuhluhOG
@kuhluhOG 10 ай бұрын
@@EmilPrivate mostly longer compile times, yes
@EmilPrivate
@EmilPrivate 10 ай бұрын
@@michaelxz1305 It's relative
@Toda_Ciencia
@Toda_Ciencia 10 ай бұрын
What's this text editor you used?
@draconicepic4124
@draconicepic4124 10 ай бұрын
I have a number of issues regarding this video: First, the example given is a exceptionally poor choice. The number of unique cases is far to few for when Polymorphism should properly be used. With only "OP_ADD" and "OP_MUL" as outcomes, the code will compile into conditional jump statements. If there were more cases, the switch statement would likely be compiled into a jump table. This would have been a far better comparison to Polymorphism. As it stands, the video was effectively comparing if-statements to function pointers for a minuscule number of cases. Second, This explanation on how vtables are structured is based on the assumption that the vtable isn't embedded into the object. While I haven't encountered any compilers that do this, actual implementation of an object's vtable isn't standardized. This video can wrongly give off the assumption that it is standardized. Thirdly, in regards to why virtual methods are slow, while it is true that memory operations are slower than operations that take place on registers, the biggest slowdown actually comes from the fact your doing a dynamic jump. Modern CPUs try to fill the execution pipeline with as many instructions as possible with out-of-order execution by looking ahead for independent chains of instructions. This is far easier to do when the control flow of the instructions is static and works especially well when the branch predictor chooses the correct execution path. When a dynamic jump occurs, the processor basically has to halt everything until the memory location of where it's going is loaded into the processor. Forth, I must disapprove of the blanket "This is Bad" approach this video takes. Polymorphism is just like any other tool in programming: there situations where it is good and bad. When choosing what mechanism to use in programming, one needs to compare the benefits and cost. The video you've done shows Polymorphism being used in an inner-most loop. This is quite likely the worst case scenario for polymorphism. It simply isn't worth the overhead that comes with polymorphism. If you wanted to stick with the calculator theme for the video, better options like Square Root, Trigonometric Operations, or Logarithm would have been a fairer comparison. Lastly, the code presented at the start has some problems. Why are you using atoi in the conditional?! The code will be pointlessly executed every pass! Additionally, if you use optimization settings, the compiler might very well optimize away the entire body of the loop! If it tries to inline the operation code, it might very well see that the only one case is ever true and the operand assignments are pointless. This would result in an addition to a temporary variable that is only written to and never read from. Seeing it has no side effects, it might just empty the entire body of the loop. Unless you looked at the assembly, you wouldn't be able to tell how aggressively it optimized the code and if the tests were actually fair.
@MrJake-bb8bs
@MrJake-bb8bs 10 ай бұрын
The video gives a clickbait feeling by bashing on something widely used so hard, that everyone using it would feel offended.
@cesarbretschneider
@cesarbretschneider 10 ай бұрын
I also would like to add that the video invites to premature optimisation. 99.99% of developers will have bigger fish to fry in their codebase than two memory accesses
@khatdubell
@khatdubell 10 ай бұрын
You left off Sixthly, he incorrectly states that you can't do OO programming in C. It requires more discipline, and you don't have the benefit of any sort of language features, but there is literally a book called "oo in c". Unless the book is several hundred blank pages, i haven't actually checked. Seventhly, sort of the the reverse of the above. If you really dying for that minuscule performance bump, you can write the code in C++ the same way you would in C. Without any late binding.
@jasonenns5076
@jasonenns5076 10 ай бұрын
​@@khatdubellMinus the restrict keyword in C, but I have never needed to use restrict.
@IBelieveInCode
@IBelieveInCode 10 ай бұрын
@@MrJake-bb8bs I don't feel offended. Am I a freak ?
@Spirrwell
@Spirrwell 10 ай бұрын
"It is important to note that by default, functions with the same signature in a derived class are virtual in the parent by default." What? No? This isn't Java.
@reroman
@reroman 10 ай бұрын
I was just looking for this comment. Maybe he's not as experienced in C++ as he is in C.
@Spirrwell
@Spirrwell 10 ай бұрын
@@reromanSure, but it's such an odd mistake. It was such a detailed explanation, but it was just wrong. Especially for a whole video built around why polymorphism supposedly sucks.
@michaelgreene6441
@michaelgreene6441 10 ай бұрын
Is this perhaps c++ version specific rather than outright wrong? (not a cpp dev just a thought)
@Spirrwell
@Spirrwell 10 ай бұрын
@@michaelgreene6441Think about what you're suggesting. C++ is old. If there were such a drastic difference in behavior between C++ versions, that would actually be terrible. This is simply wrong.
@Spartan322
@Spartan322 10 ай бұрын
@@Spirrwell It would literally mean you can't move a project further forward on C++ versions, which does happen occasionally, if you try to use auto in C++98 and and then move to C++11 it will throw an error where it was once valid, but such things are rare and not common use, auto was functionally worthless by 1995 and almost no one used the feature and it was deprecated for over a decade, and almost no other elements of the language were reworked like that, other case I can think of is the comma operator in square brackets, which again was a feature nobody used because the comma operator is almost never used for its return value. (and otherwise the implementation for a[1, 2, 3] wouldn't be how you could access a 3 dimensional container which they deemed a nightmare to do)
@hugo-garcia
@hugo-garcia 10 ай бұрын
A non-virtual call is exceptionally fast, as it usually consists of a single instruction. On the other hand, a virtual call introduces an extra level of indirection, leading to the purported 20% increase in execution time. However, this increase is merely "pure overhead" that becomes apparent when comparing calls of two parameterless functions. As soon as parameters, especially those requiring copying, come into play, the difference between the two overheads diminishes. For instance, passing a string by value to both a virtual and a non-virtual function would make it challenging to discern this gap accurately. It's essential to note that the increased expense is primarily confined to the call instructions themselves, not the functions they invoke. The overhead incurred by function calls constitutes a minor proportion of the overall execution time of the function. As the size of the function grows, the percentage representing the call overhead becomes even smaller. Suppose a virtual function call is 25% more costly than a regular function call. In this case, the additional expense pertains only to the call itself, not the execution of the function. It's essential to emphasize this point. Usually, the expense of the function call is much smaller compared to the overall cost of executing the function. However, it is crucial to be cautious because though it may not always be significant, if you excessively use polymorphism, extending it to even the simplest functions, the extra overhead can accumulate rapidly. In C++, and in programming in general, whenever there's a price, there's a gain, and whenever there's a gain, there's a price. It's that simple.
@marcossidoruk8033
@marcossidoruk8033 10 ай бұрын
First your whole line of argumentation about the copy overhead is ridiculous. No one will ever pass something big by value unless it is absolutely necessary, and in that case the copy should be considered part of the actual work the function has to do. If the function does a lot of work and is not called often then you eventually run into icache issues wich increases the overhead. If the function is called frequently the overhead adds up, doesn't matter that its small compared to the actual work that the function does, small multiplied by a big N is big, and if you are in a performance constrained system like a videogame renderer even small overhead should be considered. Another way in wich vtables are horrible for performance that this video doesn't mention is the fact that they introduce bloat into your data types wich is absolutely horrendous for cache efficiency, so even if you don't care at all about the indirect call overhead vtables may be a no starter to begin with. Also what "gain" do you get from virtual functions? I would argue you get negative benefits, codebases that uses this kind of thing extensively do a lot of inheritance wich is in itself bad for performance (bad for cache since you end up with HUGE data types) and just overall terrible for readability and simplicity. And even if you do single layer inheritance and you are very careful not to bloat your data types virtual functions greatly obfuscate the control flow of the program, because we all know foo.bar() calls the bar method in the foo object oh wait, foo has 5 subclasses and it could be calling any of those, so poor me running my program through a debugger will be met with a suprise when I hit that line. Readable and pretty are different things, the way you do it in C is not the prettiest but it is the more readable, in a switch statement you can clearly see how many "overrides" there are and what needs to be true in order for every function to be called. You can step through with a debugger and see everything that is happening and not have to worry about vtable nonsense that may be happening in your back at any moment. Never ever use virtual functions, they are always bad, and I absolutely mean this in absolute terms.
@monochromeart7311
@monochromeart7311 10 ай бұрын
​​@@marcossidoruk8033 I mostly agree with you, but virtual-functions/dynamic-dispatch still have their place. A simple example would be a video game, where the Enemy type can be anything and should be easily extensible without changing the calling code. About the object bloat, there's an alternative you may have seen in Rust - dynamic types. The vtable only exists if the type is marked as dynamic, which means the original type doesn't have any bloat, but references to it become fat pointers (pointer to object + vtable).
@saniel2748
@saniel2748 10 ай бұрын
@@marcossidoruk8033 To be fair when you have a switch statement, all possible types are known at compile time. When you use vtable you can, for example, add types from other dlls and it'll still work. These are different functionalities Also I feel like languages should introduce such closed polymorphism where virtual functions would compile to a function with a switch inside, would be kinda cool
@hugo-garcia
@hugo-garcia 10 ай бұрын
@@marcossidoruk8033 I never said any of this
@tweakoz
@tweakoz 10 ай бұрын
​@@marcossidoruk8033 The second you start running code where data loaded from storage influences the order of execution (very common in a game engine), the control-flow argument goes out the door. Yes you can still mitigate some of the chaos away by processing the execution graph into a more structured form - but some will remain anyway it will never be completely predictable. A much worse offender of control flow obfuscation than vtables are collections of asynchronous lambdas (jobs) processed by a multi-threaded worker queue - that makes me pine for the simple days of a running a method on a collection of base classes with vtables.
@abaan404
@abaan404 10 ай бұрын
Cool video but i could'nt stop staring at the cpu with the swick sunglasses
@LowLevelLearning
@LowLevelLearning 10 ай бұрын
good ole carl
@Basicguy1798
@Basicguy1798 10 ай бұрын
Exactly... Me too
@Mitch-xo1rd
@Mitch-xo1rd 10 ай бұрын
In the famous words of Chef "There's a time and place for everything, children. It's called college"
@bigtymer4862
@bigtymer4862 10 ай бұрын
So true 😂
@gronkhfp
@gronkhfp 10 ай бұрын
Or enterprise grade software 😂
@user-og6hl6lv7p
@user-og6hl6lv7p 9 ай бұрын
$70K for something you can otherwise learn online by yourself? Nah. Plus modern college is predominantly online anyway. Professors don't have time to answer intricate questions and tell you to google things anyway. Absolutely pointless.
@cgme9535
@cgme9535 9 ай бұрын
@@user-og6hl6lv7p that’s quite the generalization. All universities and differing degree programs are different.
@teaser6089
@teaser6089 9 ай бұрын
@@user-og6hl6lv7p that's not the case in The Netherlands, don't generalize your experience with other countries.
@tzimmermann
@tzimmermann 10 ай бұрын
I've been coding a game engine from scratch for a few years now, and in most real scenarios I encounter, where functions perform actual work, the virtual call overhead is just negligible. I tend to avoid using virtuals in a hot path (functions that will run many times per frame), but I'm totally fine using them elsewhere. This is how I dispatch game system update and render calls in my framegraph, for instance. My game systems must have state, and the engine can't know about the specifics of client-side game system implementations. All it knows is that some will modify a scene (update), and some will push render commands in a queue when traversing a const scene (render). So polymorphism is a good tool here: it makes the API clear enough, it makes development less nightmarish, the abstraction it introduces can be reasoned about, and the call overhead is jack shit when compared to execution times. Guys, don't let people decide for you what "sucks" and what is "nice" or "clean". This is ideology, not engineering. Toy examples like this are *not* real code, measure your actual performances and decide for yourself.
@Mitch-xo1rd
@Mitch-xo1rd 10 ай бұрын
Yep, you can believe in whatever programming ideologies you want, but it all goes out the window when you are in a real world situation and actually need to get something working.
@jaskij
@jaskij 9 ай бұрын
I can't find a source on this, but I remember reading around a decade ago, when I started uni, that the OGRE engine was made the way it is was partially to demonstrate that the overhead of virtual calls is neglible.
@tzimmermann
@tzimmermann 9 ай бұрын
@@jaskij Nice, I didn't know about this! Can't find a source either, sadly.
@guillaumebourgeois42
@guillaumebourgeois42 9 ай бұрын
If you can ever find it back, I'd be thrilled to read about it @@jaskij !
@Takyodor2
@Takyodor2 9 ай бұрын
You deserve way more upvotes, the example in the video isn't a typical scenario where you'd want to use polymorphism at all.
@pheww81
@pheww81 10 ай бұрын
I get your point: virtual function have a cost and it's true. But the video is not 100% honest. The c++ code can be extended by only creating new type of operator without touching the code that execute operation. The C code cannot, you must change the central switch case and the enum. The virtual function has a cost but also offer functionality. Do the functionality worth the cost? Maybe yes maybe no. Each case is different and must be evaluated. Also c++ != virtual function and inheritance. It's not because the feature exist you must use it. They are tools in the toolset, nothing more. Then you also did an useless operation the force the c++ to pass by the vtable with the line "Operation *op = &add;". If you just used "add.execute();" directly the compiler you know at compile time what function to call and would not pass by the vtable. I understand you did that to have a one page example. But it could lead someone to think an example this simple would always use overkill feature like the vtable. It make look c++ dumper than it is.
@crimsonmegumin
@crimsonmegumin 10 ай бұрын
I agree. In fact, it's probably zero cost if you use templates (at least in Rust). In this case, he is using a pointer (in Rust, it would be `&dyn Operation`, the dyn makes it clear it's a fat pointer)
@nero008
@nero008 10 ай бұрын
you only have to write it once but the user will have to pay the runtime cost forever !
@theEndermanMGS
@theEndermanMGS 10 ай бұрын
@@nero008You do know that the vast majority of useful code needs maintenance well into the future, right? Not to mention that this sort of thinking almost always falls under the umbrella of premature optimization. Outside of very select circumstances, the performance difference between a polymorphic implementation and a monomorphic implementation is utterly imperceptible to the end user, but the difference it makes to the structure of the code could be very well noticed and appreciated by any programmer doing work on that code
@nero008
@nero008 10 ай бұрын
@@theEndermanMGS why the hell would i base my point on cold paths ?
@theEndermanMGS
@theEndermanMGS 10 ай бұрын
@@nero008 You certainly didn’t read that way, given that you were responding to a critique of the video’s lack of nuance that makes clear that there is room for evaluating whether a virtual call’s performance penalty is worthwhile in a given case. I don’t see any reasonable way to read your response except as a complete dismissal of the more nuanced view.
@Minty_Meeo
@Minty_Meeo 10 ай бұрын
Remember to mark your polymorphic classes as final if they are the final derivation! That lets your compiler optimize virtual function calls into regular function calls in situations where there can be no higher superclass.
@jeffspaulding9834
@jeffspaulding9834 10 ай бұрын
You can write OO code in C. C just doesn't do the heavy lifting for you. You can write your own dispatch tables with function pointers. There are plenty of OO libraries for C. GTK+ and Xt are two examples. The code doesn't look as nice, but OO is about how you organize and reason about your code, not about whatever syntactic sugar your language gives you.
@GregMoress
@GregMoress 10 ай бұрын
A feature of the language is not just 'syntactic sugar', because if it were, then C is just 'syntactic sugar' for Assembly.
@TsvetanDimitrov1976
@TsvetanDimitrov1976 10 ай бұрын
While all of this is true, I think the main point of the video was that type erasure is just not a good idea in general for a staticly typed language from an efficiency pov, so c++ making it easier is a debatable choice(at least in nowadays perspective, I'm not suggesting that Bjarne should have looked at his crystal ball to see that in the future the additional level of indirection would mess with the branch predictor or the icache)
@taragnor
@taragnor 10 ай бұрын
Right, OOP is a paradigm, not a language feature. C++ used to originally just compile into C first before it got its own compiler. You can do almost anything in C, it just may take a godawful amount of boilerplate that other languages will handle for you with ease.
@valizeth4073
@valizeth4073 10 ай бұрын
​@@GregMoressNo, in fact C doesn't even target your CPU (thats what your compiler does), C targets an abstract machine.
@GregMoress
@GregMoress 10 ай бұрын
You are bluring the line I drew, so allow me to blur yours... In Java and DotNet, and even Basic, there is/are intermediate OP Codes, MSIL for DotNet, ByteCode for Java, and P-Code for BASIC. It's possible for C to have such 'intermediate' Op codes as well, which is the Assembly language that programmers would write to if not for hi-level 'syntactic-sugar' languages... The fact that C doesn't use OP codes doesn't change the fact that a programmer COULD write Assembly targetting their CPU. An internally, the C compiler DOES have platform-agnostic OP codes. The difference between C and C++ is not just 'syntactic sugar' it is a feature, just as C is a feature, and not just 'syntactic sugar' over OP codes.
@sledgex9
@sledgex9 10 ай бұрын
Mistake in 03:46: If a derived class has the same function as the parent BUT the parent function isn't marked as virtual it doesn't become virtual implicitly. The 2 functions will co-exist simultaneously. Which one will be called depends on the type of the variable that does the call. Is it base class variable or a derived class variable? Specifically if the pointer is of type base*, you assign it to an instance of derived class and call the overriden function it will actually call the base class's implementation of that function. This is a subtle C++ pitfall that can lead to bugs. What you probably meant to say: If a base class has marked a function as virtual then it becomes implicitly virtual for any derived class. The derived class doesn't have to explicitly mark it as virtual. But for clarity reasons it should do so.
@TheSimoriccITA
@TheSimoriccITA 10 ай бұрын
This video shows a only a compaison when C++ virtual approach is disadvanaged and dosen't count the disadvantages of the C switch approach, using that to arrive at a simplistic conlcusion "virtual bad". The example show in this video the number of operation and the operation itself are very small, so here the comparison is "dereferencing a virtual table and call a function" vs "call diretly a slightly bigger function". But if we pass in a more realistic context, where there are more derived classes and bigger method, now in the C approach we have bigger switch that've to call other functions so that means that we've to do 2 function call in C approach insthead of 1 like the C++ approach, with all the overhead that comes to (or put all the logic of all cases in one function and have a massive switch). And THE CACHE MISS CAN HAPPEN EVEN IN C APPORACH TOO, you know the switch has to be loaded form memory too, like a vtable. The C switch has the advantage of being easier to optimize and can be faster, but you've to now and implement the call for every type of operation in the base class. This means that it's not possible to use in situations like extending the functionality of an already compiled library. And last which compiler and flags were used?
@jonbezeau3124
@jonbezeau3124 10 ай бұрын
I teach college C/C++ and I think the most important part of introducing C++ is explaining the problems that the language was written to solve; they were problems with large scale software development, not writing faster algorithms. Projects were becoming really hard to manage with library name conflicts, simultaneous changes to common code stepping on eachothers toes, human problems like that. Polymorphism lets developers build on top of eachother's code in a way imperative code can't support. Someone could subclass your calculator, add/remove/change operators, and pass the new calculator into existing calculator-using code with no change to your calculator class code and just instantiating a different class in code that depends on your calculator.
@teaser6089
@teaser6089 9 ай бұрын
Indeed, it's a common theme with these KZfaqrs that they take very simple coding problems and think they can extend those experiences to all coding problems, but when you are not alone working on your own codebase and instead of a team of half a dozen, or god forbid dozens of people and you don't implement polymorphism you are going to grind progress to a halt needing to rewrite a bunch of code and any new members to the team need to first spent weeks or months studying the entire codebase...
@hanspeterbestandig2054
@hanspeterbestandig2054 9 ай бұрын
Bravo! 👍👏👏👏 Exactly!
@Turalcar
@Turalcar 9 ай бұрын
Polymorphism is all over the place in linux kernel (written in C). They just have to define vtables manually.
@carlynghrafnsson4221
@carlynghrafnsson4221 10 күн бұрын
You made me think of C#. I appreciated the interface there, and realized quality GUI frameworks require OOP. There are these use cases as important as selecting the right language for the job at hand. If you make a backend or low level dev look at some AI models, they will flip out. No, go ahead and do that functionally. I'll check up on you in ten years.
@francoisgagnon3529
@francoisgagnon3529 10 ай бұрын
80% to 90% of code execution time is spend in 10% to 20% of the code. if your code is performance tuned so much that it's the performance degradation is due to vtable lookup (which I highly doubt), then perhaps you can say "polymorphism sucks". For 99.999% of all programs out there, that's not the case and polymorphism is a very useful feature and makes code simpler.
@taragnor
@taragnor 10 ай бұрын
Yeah this is an extreme example because in this case, the polymorphic function in question is just: int add(int a, int b) {return a+b;} And yeah, if you're doing a bunch of one line functions it'll probably make your polymorphism overhead seem really bad. Polymorphism isn't designed to optimize toy programs, it's designed for large projects.
@delphicdescant
@delphicdescant 10 ай бұрын
You say 99.999%, and your average programmer unhappily employed doing webdev might be able to agree. The problem arises when these techniques and philosophies seep into applications programming (or worse, embedded) where the bottleneck is no longer some disgustingly lethargic network activity. Then your 99.999% becomes merely "sometimes." Are 99.999% of all web-free programs I/O bound? No, not remotely. And for any of them that aren't bound by even disk I/O (nevermind the abyss of network I/O), a benchmark between polymorphic and non-polymorphic solutions will show a non-trivial difference quite a bit more frequently than you're claiming. That small bit of code you mention that occupies the most runtime? That might be a tight logic loop or some recursive tree/graph-walking thing, which isn't that unusual for a program that's algorithmically interesting. And yeah, you'll absolutely see a difference if that logic is running through some enterprise-grade polymorphic mess. So it's truly unhealthy that OOP and web/enterprise laziness, which is excusable in that one specific realm, has infiltrated universities to become embedded in the minds of all graduates, when only *some* of those graduates are going to be employed writing code that's already so slow that polymorphism is just a drop in the bucket. (And yes, I do understand your point applies to these performance-relevant programs wherein the bulk of the computation time lies within the bodies of functions rather than straddling calls that may or may not be subject to a vtable lookup, but if you want to make that argument more effectively, you'd need to shift that rectally-extracted statistic from 99.999% to something more like 80% to avoid being too obviously hyperbolic)
@ErazerPT
@ErazerPT 10 ай бұрын
@@delphicdescant True. Recently was doing some benchmarking on array performance in C# and... let's say that [y*width+x]!=[x,y]!=[x][y]. And none of those is the same as objects encapsulating them. And none of the former is the same as accessing said objects through an interface. That said, such is the cost for "generic not specific". But... if you're laying tracks for a trolley car, you have a LOT more tolerance than if you're laying tracks for a bullet train. People just need to be taught "when is which". And I bet most will want to stick to either side of the fence in their work. "Application coders" and "performance coders" are intrinsically different, but both have their place because business needs vary.
@jomo_sh
@jomo_sh 10 ай бұрын
@@taragnor its designed for projects that dont require hyper performance, if your project is big but needs high performance then you will start needing to not use these abstractions depending on how much optimization you need and where the bottleneck is.
@T0m1s
@T0m1s 10 ай бұрын
"polymorphism is a very useful feature and makes code simpler" - do you have an example of code made simpler by polymorphism, or are you just repeating the marketing ads of OOP people?
@metal571
@metal571 10 ай бұрын
It's always worth noting that when Stroustrup says that C++ obeys the zero-overhead principle, that in NO way means that the abstraction itself is free. It's just that you couldn't hand-code it better yourself with less performance overhead to use that feature (ideally). If you use inheritance with virtual member function overrides, you *will* pay a cost, because if you care about performance 1. you should always measure it, and 2. you should be aware of exactly how it is implemented. Otherwise, don't solve the problem that way. There are some cases where inheritance is quite applicable, but needless to say, it is not exactly cheap, and its depth should be minimized if it's used at all. And to quote Gang of Four, "Favor object composition over class inheritance".
@heavymetalmixer91
@heavymetalmixer91 10 ай бұрын
I'm a newbie to OOP and I hear "composition" quite often but I don't get what it means. Can you please explain it in a few phrases?
@metal571
@metal571 10 ай бұрын
@@heavymetalmixer91 it's as simple as including one or more instances of a class inside another class rather than deriving from a base class. So a class that contains objects of another class as opposed to deriving from another class. This is easier to understand especially compared to multiple levels of derived classes
@marcossidoruk8033
@marcossidoruk8033 10 ай бұрын
Starsoup is full of shit and according to your definition "zero overhead" is actually not zero overhead. This is like killing some people and then saying "I killed zero people according to the zero kill principle because you couldn't have done any better" its nonsense. Zero overhead means zero overhead, what else would it mean? That is the abstraction costs no extra cpu cycles and that is exactly what starsoup means, he just never claimed vtables to be an example of what he calls a zero overhead abstraction. However, the whole idea of zero overhead abstractions is in itself ridiculous, no abstraction ever has zero overhead, and if it does it is probably a terrible abstraction that doesn't provide you with much abstraction to begin with.
@maksymiliank5135
@maksymiliank5135 10 ай бұрын
@@heavymetalmixer91 Instead of extending the base class you put the object of that class inside of another class. That way you still have access to all of the fields of the "base" class but usually you need to write some additional code like setters and getters and method wrappers in the new class to get to those fields and methods. In case of c++ you can achieve exactly the same result by inheriting the base class without using the virtual keyword. The fields of base class are just going to be included in the structure of derived class and methods will be statically resolved at compile time (no vtable and no double dispatch). But it doesn't work that way in other languages. For example in java every method is virtual by default.
@sanderbos4243
@sanderbos4243 10 ай бұрын
@@heavymetalmixer91 As a practical example, say you have a Bow and a Sword class, and you want to make a SwordBow class that is like a Sword and also shoots an explosive arrow out of the sword. You could do this by letting SwordBow inherit Bow and Sword, and then overriding the inherited Bow's shoot() method (function) to replace it with a new method that shoots an explosive one. With composition on the other hand, you simply tell Sword and SwordBow to both use a slash() method, and you then define the explosive shoot() method in SwordBow without overriding anything. Composition turns a 2D family tree into a simple 1D list of family members.
@givememorebliss
@givememorebliss 10 ай бұрын
Incredibly misleading. You're not comparing the same things. Full-fledged polymorphism with virtual calls cannot be compared to a case over an enum. If you were comparing C++ to a function pointer call (with the destination of the pointer compiled in a separate TU and the program compiled without link-time optimisation) in C, that could be considered "comparable" at least.
@tomb5372
@tomb5372 10 ай бұрын
I think you're missing the point a bit. Every competent C++ knows that virtual functions aren't the most efficient. Using them in "hot" code (e.g. tight loops) causes the "penalty" to exponentially grow. But it's a way to abstract things. The C example here doesn't really abstract anything, whereas the C++ example does. You could compile and run the same C example code in C++. You could change it to use classes (which are just structs after all). Heck, unlike C, you could even turn it into templates and do all kinds of fun stuff and get the compiler to even inline the whole thing, making it potentially even more efficient than the C counterpart. Bottom line is, polymorphism doesn't really "suck" for this reason. The biggest complaint is the complexities that come info play when you inherit from multiple classes at the same time, which many languages don't even allow.
@zeez7777
@zeez7777 Ай бұрын
Why would it grow exponentially? It should be linear right?
@NysShortCut
@NysShortCut 10 ай бұрын
In large project, polymorphism could literally save tons of amount of time and be really helpful to organize the code. Things like OOP, design patterns, are basically just for large projects but not for something small like calculator or some kinda stuff.
@calebsteinmetz9471
@calebsteinmetz9471 10 ай бұрын
Exactly, they aren't done to speed up execution time, they are done to speed up development time
@eddiebreeg3885
@eddiebreeg3885 10 ай бұрын
Thing is, they don't always make your code easier to write. Using polymorphism often comes from the assumption that your code has to be generic on order to be future proof and easily scalable. But the reality is: very rarely do you actually know whether you will need this extra scalability in the future, ESPECIALLY in large projects. Using stuff like enums, or sometimes type erasure doesn't have to be that complicated (I mean enums are really simple, seriously) and they can minimize the performance overhead. Polymorphism can be okay, if you KNOW that you need it for any given task. If you're just trying to plan ahead chances are you will mess it up because no one is that good. Maybe it's okay when you only have a few polymorphic classes with a single inheritance level... but when your WHOLE API is almost exclusively polymorphic, with sometimes 3 or 4 levels, it's a nightmare performance wise, for a minimal benefit
@calebsteinmetz9471
@calebsteinmetz9471 10 ай бұрын
@@eddiebreeg3885 Agreed, if you try to use polymorphism on everything you are going to have a bad time. I look at it more as a tool for very specific things. Any feature of a language can be bad if used without care.
@taragnor
@taragnor 10 ай бұрын
The thing with OOP is it's far more flexible. Imagine you wanted to add a new operation. Using the C method, you'd need to be able to directly alter the original source code. If you're using an external library, that may not always be an option. Under C++ polymorphism you can build a library that can be extended with new operations simply by extending the base class and you never need to alter the base code. And that's one of the main advantages of OOP, being able to create libraries of classes and functions that people can just drop into their programs and use.
@mskiptr
@mskiptr 10 ай бұрын
(or you could just use FP and have nice abstractions from the start)
@MyManJohnny
@MyManJohnny 10 ай бұрын
I'd say that polymorphism doesn't necessarily suck. Poor use of polymorphism sucks. If your virtual function takes only couple of cycles to complete, then it might not be a good idea to use polymorphism, but when it takes couple of miliseconds, the slowdown caused by the extra lookup is negligible.
@panzach
@panzach 10 ай бұрын
Nailed it.
@joeroeinski1107
@joeroeinski1107 10 ай бұрын
Sounds deceptive to say it sucks. It doesn't. However, it's easy to misuse.
@homelessrobot
@homelessrobot 10 ай бұрын
some might say that tools that are easy to misuse categorically suck more than tools that aren't easy to use at all, though that wasn't his point, so that wouldn't help his case.
@valizeth4073
@valizeth4073 10 ай бұрын
I mean this guy just doesn't know what he's talking about, which is rather apparent. This whole video carries the same argument as "random access data structures suck! they're slow!" when all he's doing is performing tons of insertions and deletions at the middle of the list. He takes 1 example and compares it to another completely abused case of a language feature and draws the conclusion that it "sucks".
@EmilPrivate
@EmilPrivate 10 ай бұрын
Firstly, I'd like to emphasize that polymorphism, like any tool in the programmer's toolbox, has its optimal use cases. It's not inherently 'bad' or 'good', but should be used when appropriate for the problem at hand. An overly narrow focus on performance can inadvertently lead to premature optimization, and it's important to remember the classic Donald Knuth quote: "Premature optimization is the root of all evil". Polymorphism shines in scenarios where you need to process a collection of objects that share a common interface but have different internal behaviors. In these cases, it allows you to write more concise, readable, and maintainable code by avoiding verbose switch-case statements or if-else ladders. Although polymorphism introduces a performance overhead due to the indirection through the vtable, this is often insignificant in comparison to the cost of the actual function execution. And while it's true that in a tight loop this overhead can accumulate, this isn't where polymorphism typically brings the most value. To your point about how vtables are implemented, it's true that there's no standardization across compilers. However, most C++ compilers adopt similar strategies, and the vtable is usually stored separately from the object instance, with a pointer to the vtable added to the object's memory layout. This additional level of indirection can indeed add to the function call cost, and that cost is further compounded by the challenge for CPUs to predict dynamic jumps, as another commenter rightly pointed out. I also agree with the critique about the video's simplistic example. A more complex set of operations would have made the comparison more meaningful, as the benefits of polymorphism become more apparent when you're dealing with larger codebases and more complex behavior differences between classes. Finally, it's worth noting that C++ offers more than one way to achieve polymorphism. Beyond the classical (runtime) polymorphism we're discussing here, there's also compile-time polymorphism, also known as static polymorphism, via templates. The latter can eliminate the runtime overhead, at the expense of potentially increasing the binary size. It's yet another trade-off to consider based on the specific needs of your program. Overall, I think it's crucial not to dismiss polymorphism out of hand because of performance concerns. Instead, we should understand its costs and benefits, and use it where it makes the most sense.
@T0m1s
@T0m1s 10 ай бұрын
"Firstly, I'd like to emphasize that polymorphism, like any tool in the programmer's toolbox, has its optimal use cases" - name one.
@EmilPrivate
@EmilPrivate 10 ай бұрын
@@T0m1s Well if you had read a bit further than just the first few lines you'd see that I'm providing actual examples, starting at "Polymorphism shines in scenarios...". I will forgive you though since attention span is a common issue these days. Below is another example that will further elaborate this. Suppose we have a number of distinct shape classes, such as Circle, Square, and Triangle. Each of these shapes can be drawn and resized, but the specifics of how these operations are implemented vary between each shape type. By defining a base Shape class with virtual 'draw' and 'resize' methods, we can implement polymorphism. Through this approach, we're able to maintain a collection of Shape pointers, without needing to know the specific type of shape they point to. When we need to draw or resize a shape, we simply call the appropriate method. The correct implementation is chosen at runtime based on the actual object type, thanks to the magic of polymorphism. This not only makes our code more concise and maintainable (as we're not wrestling with unwieldy switch-case statements or if-else ladders), but also more extensible. If we decide to introduce a new shape type into our program, we simply create a new class that extends Shape and provides its own 'draw' and 'resize' implementations. No need to touch existing code. Polymorphism thus allows us to write code that is open to extension but closed for modification, embodying a key principle in object-oriented design, known as the Open-Closed Principle.
@davivify
@davivify 9 ай бұрын
I LOVE polymorphism. Let me tell you why. I've used it in providing an undo/redo feature, as well as in writing an arithmetic parser. It elegantly separates the overall design from the implementation of nodes. So if I have to fix nodal behavior or add new node types over time, I don't have to touch the top level architecture. It. Just. Works. Yes, there is a small performance hit but it's more than worth it to me to have more readable, more maintainable code. That said, one alternative is to use name overloading (what I like to call "static polymorphism"), which is resolved at compile time and solves the performance hit.
@sharksandbananas
@sharksandbananas 10 ай бұрын
This is a nice video but I think it's also a bit misleading... The C code isn't *really* polymorphic: Every 'method' added has its own switch statement, every 'child' added is a new case for every switch in every function and the monolith grows. And the C++, of course it'd be silly to use that kind of indirection on the hot path (and std::visit(); might be preferable). Either way, the compiler will optimize away where possible for the target platform, including switch statements.
@01001000010101000100
@01001000010101000100 10 ай бұрын
You explained very nicely why polymorphic code is slower, but it sucks only if you need to squeeze all possible performance from the hardware by a specific task. Which is the case pretty rarely. The most common scenario is something (I mean, most of the code) is waiting for something slower, meaning it has more than enough time to not create any measurable lag. But yes, when you're optimizing a tight loop and every cycle matters, then the good old C is the way to go. But again it would be pretty tricky to provide a right example. In my practice - if I have a tight loop and I want to save every cycle - I just avoid calling anything, inlining as much as possible. And this can happen inside a C++ overridden method, but it just doesn't matter as long I don't call other other methods from said tight loop. Sometime I mix C with C++, where C++ is for general app logic, and C is doing time critical things. The common C++ usage is for tasks too tedious to code in C and usually not time critical. Like UI and network. The code waits "ages" for something to happen, then you invoke a C function that does the heavy lifting pushing gigabytes of data because the user just moved a mouse or clicked a button.
@teaser6089
@teaser6089 9 ай бұрын
Yeah the title should have been specific for C++, instead he chose to clickbait and generalize it to all code, which is not the case. If you need to develop software with a team of people not using polymorphism will cause more problems than the potential performance loss, which in most real world cases that aren't basic C++ scripts running on Arduino don't exist in the first place.
@giorgiobarchiesi5003
@giorgiobarchiesi5003 10 ай бұрын
Good point, but in the calculator example, the C code has conditional code that the C++ code does not have. Believe me, after having programmed in C++ for 30 years, I can assure you that the supposed overhead of polymorphism is negligible in the overall execution time of a real world program.
@maxp3141
@maxp3141 4 ай бұрын
Depends on the specific case. If you loop over 100 million elements and call an add function (virtual or otherwise, as long as it’s not inlined) in every iteration then it’s way heavier than just summing the numbers in the loop. As always, it’s about choosing three right tool for the right job.
@giorgiobarchiesi5003
@giorgiobarchiesi5003 4 ай бұрын
@@maxp3141 Ok, but this is why I specified “real world program”. I have no experience of real world programs that loop a million times calling a function that merely performs a sum. Functions are typically more elaborate than this, so I think that my reasoning is sound.
@justaboy680
@justaboy680 10 ай бұрын
Not many people can make an 8min long video about polymorphism and such low-level details and still keep it interesting. Well done, bro.
@LowLevelLearning
@LowLevelLearning 10 ай бұрын
Wow, thanks!
@valizeth4073
@valizeth4073 10 ай бұрын
Just unfortunate that he was wrong regarding pretty much everything.
@justaboy680
@justaboy680 10 ай бұрын
@@valizeth4073 I found the video very informative and engaging. Can you specifically address where he went wrong?
@ccreutzig
@ccreutzig 9 ай бұрын
​@@justaboy680 E.g., the claim that because C is not an OO language, you cannot write OO code in it. Of course you can. What do you think fopen/fread/fclose do, if not return and work with an object handle with virtual member functions?
@justaboy680
@justaboy680 9 ай бұрын
@@ccreutzig I'm not aware of the implementation details of file utilities. What virtual member functions are you talking about?
@SophieJMore
@SophieJMore 10 ай бұрын
Polymorphism doesn't need to involve dynamic dispatch though. In Rust, for instance, polymorphism can be achieved with generics. And the cool thing about generics is that at compiler time they get monomorphized, i.e. the compiler will just copy-paste the function with every combination of concrete types that it's called with, so you just end up with roughly the same setup you had in C with no performance cost. I haven't had much experience in C++, so I don't know how templates work there, but presumably it would be possible to do the same thing there. Also there are situations where dynamic dispatch is needed, so, if you're writing it in C you'd use function pointers and it would be just as slow as virtual methods.
@KokahZ777
@KokahZ777 10 ай бұрын
For such simple operations vtable lookup represents 20% more of execution time but truth is in a real program it would not represent such a high proportion of execution time. And C code might be less easy to maintain down the line
@taragnor
@taragnor 10 ай бұрын
Yeah most functions aren't going to be "return a + b;"
@enderger5308
@enderger5308 10 ай бұрын
C can be written in OO style (see GObject), it just requires you to manually implement the pattern. An interface in C would be a structure containing function pointers constructed by object specific functions.
@sinom
@sinom 10 ай бұрын
std::function, lambdas, concepts etc. v-tables and virtual are rarely actually required and usually just a convenience tool for doing something in a specific way.
@ribamarsantarosa4465
@ribamarsantarosa4465 9 ай бұрын
Respect for taking the time to write such a critical video. A suggestion I make when writing critical things about a programming language is to let clear when the criticism goes on the language itself (e.g. syntax), and when it goes on implementation aspects. Your criticism is on an implementation aspect, that can be optimized by a compiler. So, if you think well, your criticism isn't on (C++'s) polymorphism.
@Cjw9000
@Cjw9000 10 ай бұрын
It's not always about performance, you have to choose wisely what you want to use for your particular use case. If your use case requires raw performance, OOP may not be the way to go. But if you don't want to spend 5 hours debugging your C code, maybe OOP is the way to go. Still, it's very nice to see someone explaining the implementation details of C++. Great work!
@filipg4
@filipg4 10 ай бұрын
Why would a procedural code be a mess? It's usually really flat, as opposed to OO code. Which in my experience makes it a lot easier to debug, there's a lot less to worry about overall, functions are the only abstraction mechanism. Sure C++ compiler is better, which why people write C-style C++ over C.
@godnyx117
@godnyx117 10 ай бұрын
Or you choose a language that doesn't suck. OH, WAIT!!!! There isn't any! Until I finish and publish Nemesis that is!
@Cjw9000
@Cjw9000 10 ай бұрын
​@@filipg4 I didn't mean procedural code in general, but highly optimized code.
@empireempire3545
@empireempire3545 10 ай бұрын
"if you don't want to spend 5 hours debugging your C code, maybe OOP is the way to go. " - OOP changes 5 into 50-500 hours, which is good for job stability
@lazergenix
@lazergenix 10 ай бұрын
I only use virtual functions for my callback classes, it's nice to have syntax that wraps up functions in a single class and doesnt need to use C's terrible function pointer syntax. Never heard someone using it for "debugging" reasons
@on-hv9co
@on-hv9co 9 ай бұрын
About the V-Table, if you use the final specifier on virtual functions this(amongst other things) provides the compiler an opportunity to "devirtualize" the function.
@rastersoft
@rastersoft 10 ай бұрын
I think that the concept of this video is wrong from the base. I mean: polymorphism sucks... if you need performance. But when what you need is code easily maintenable, and the performance is not a problem, it is a good tool (but don't forget what happens when you only have a hammer...). If performance were the only metric, we would use only assembler/C, and never languages like python, perl or java.
@lazyh0rse
@lazyh0rse 10 ай бұрын
The c code in the video already looks so much uglier than the c++ version. Just imagine building huge libraries such as image adapters. It would look like spaghettis for sure in the C code.
@xKeray
@xKeray 10 ай бұрын
lmao, we would absolutely NOT stop using java if performance was the only metric - we would stop with enterprisey patterns though
@jonhdoe4119
@jonhdoe4119 10 ай бұрын
Meh. Try writting java on a memory constrained device or for real-time systems.
@drno87
@drno87 10 ай бұрын
I used polymorphism in writing simulations where the code paths were things like "run this CFD code that takes 20 min" vs "run this approximation that still takes 30 seconds". It made for convenient organization where the child classes would track whatever extra data they needed. The overhead in using virtual functions maybe added a few millionths of a percent to the overall runtime of that particular feature.
@stuartlovett
@stuartlovett 10 ай бұрын
Enjoy your videos, it's great to see videos explaining the inner workings of the stuff that we just take for granted now. I think you may have miss spoken a couple of times around 3:32. The "=0" is not needed in order for the compiler to create the v-table entry, this just indicates that the class is only to be derived from and that the derived class must implement this pure virtual function. Non-pure virtual functions (without the =0) need not be implemented in the derived class if the base implementation is appropriate. Also you say that the virtual keyword is not needed and that by simply declaring a function with the same signature in the derived class the compiler will assume the base method to be virtual. I think this is true in some other OO languages, but not in C++. You cannot make a base class virtual without specifying the virtual keyword in the declaration of the base class. It is true that you can declare a function with the same signature in the derived class - this is know as "overriding" (which is not the same as "overloading") - but it will not get an entry in the v-table and will therefore not get called by a method invocation from a base class object. The following g++ program demonstrates this: #include class base { public: void identify(void) {printf("%s ", __PRETTY_FUNCTION__);} virtual void virt_identify(void) {printf("%s ", __PRETTY_FUNCTION__);} }; class derived : public base { public: void identify(void) {printf("%s ", __PRETTY_FUNCTION__);} virtual void virt_identify(void) {printf("%s ", __PRETTY_FUNCTION__);} }; int main(int argc, char *argv[]) { derived d; base *b = &d; b->identify(); // calls base::identify() (not a virtual function) b->virt_identify(); // call derived::virt_identify() (via vtbl) d.identify(); // calls derived::identify() (overrides base::identify()) d.base::identify(); // calls base::identify() (explicitly) b->base::identify(); // calls base::identify() (explicitly, bypasses the vtbl) return 0; } Program output: void base::identify() virtual void derived::virt_identify() void derived::identify() void base::identify() void base::identify()
@didgerihorn
@didgerihorn 10 ай бұрын
The basic point you're trying to illustrate about the vtable is correct, but I think we have to clarify a few things. First of all, this is not about C++ but about the code style your're using (runtime polymorphism). If you hadn't used a manual way of creating a function pointer, the compiler would most likely have resolved the polymorphism at compile time. Speaking about compile time, you could also use templates for enforcing compile-timepolymorphism. And modern CPUs are good at resolving indirections like vtables, too. All in all, OOP and polymorphism can be great for structuring code, but I think they're overused.
@anacierdem
@anacierdem 10 ай бұрын
C++ as a language, is capable of solving the problem introduced in the video faster than C contrary to the argument. If you need to have a “dynamic” operation (like a custom operation for a mapper or a predicate for a filter), on C you are stuck with function pointers (the given enum example is unrelated to the problem space IMO, it is fully static and not bound late at execution time) and they cannot be easily inlined by the compiler as they are only known very late at execution time. C++ OTOH, is capable of doing the inlining at compile time thanks to the superior type system. Overall I think this problem is a pretty bad example for polymorpism. It is just not the correct tool for this use case. Of course there are many ways of achieving the same thing and some of them will perform slower and it is something to consider when picking a solution. For the provided example, a compile-time polymorphism strategy (ie changing the operation at compile time) would perform the same as the C version provided. Note that the C version would not be versatile as you cannot simply provide the operation at the callsite and you’d need to modify every place you want to use that operation without a function pointer.
@skejeton
@skejeton 9 ай бұрын
it cant really inline without LTO, and if you define it in the header, it can inline it, but same would apply to C, if you use a custom vtable
@felipelopes3171
@felipelopes3171 10 ай бұрын
Well, this has been discussed dozens of times already. Essentially the example you constructed is specially crafted to make polymorphism look bad. If you want to do a simple calculator, the operation can be done in a single instruction and fits in register. For the types of code that OOP was designed, though, it has to search data structures which use memory allocated in the heap, fetch data from storage, invoke kernel syscalls, etc, and all of this will make the overhead of calling a vtable negligible. And even in the case that you find yourself in a situation where you need to have the CPU call an operation millions of times you could just dump the class structure with other code, translate it to C style opcodes and run this instead. This is what any numerical library in a virtualized language does, btw. And if you do your calculation in the methods it provides, instead of using the language directly, it's just as fast as C code. At the end of the day, the only performance penalty you cannot get away if when using higher level abstractions is the start up time it takes to initialize all the stuff, and a fixed amount of memory to store everything. I definitely think yoh could provide better content to your viewers than something which already has been exhaustively discussed in the 80's.
@hexadecimalpickle
@hexadecimalpickle 10 ай бұрын
Nice video! There's a miss at 2:15: C++ classes are not C structures with function pointers. That's true only for polymorphic classes - and not even for all types of polymorphism, if we have to be picky. C++ classes are basically just structures, with member functions being normal functions with an implicit "this" argument. It's also worth mentioning the use of the "final" keyword can help assisting the compiler in optimising away virtual table lookups when virtual functions are called via the final implementation. Still, I prefer the C way as it doesn't add hidden data members and doesn't perform the shenanigans I described in this comment. Much easier to reason about.
@haiphamle3582
@haiphamle3582 10 ай бұрын
An alternative title: "How C++ implements polymorphism and its cost"
@DjoumyDjoums
@DjoumyDjoums 10 ай бұрын
Hence why devirtualization is a big subject for compilers, making this problem disappear when possible. Using sealed classes at the end of the inheritance chain helps for devirtualization.
@junbird
@junbird 10 ай бұрын
Fun fact: doing all of that stuff in C is completely feasible, altough it's unconvenient (lots of boilerplate code is necessary, C just is not object oriented) and unsafe (you need to do type checking at runtime). I actually discovered these things on my own a few years ago, when I first began studying OOP and tried to replicate it in C, which was the only language I was familiar with at that time. When you actually implement this stuff, you realize how much wasteful it is. Btw, please note that there are many types of polymorphism. This is specifically what's known as inclusion polymorphism, which as far as I know is the only kind of polymorphism which has to be implemented with these type of runtime mechanisms. However, polymorphism is not something which is exclusive to OOP and stuff like adhoc polymorphism (ie function overloading or even traits in Rust) and parameter polymorphism (ie templates) are usually implemented statically. I know that there are a few other kinds of polymorphism, but I don't know much about those.
@proud22beme
@proud22beme 10 ай бұрын
I appreciate how this is actually a technical look at HOW and WHY, rather than just says "this is slow and bad" Showing the ASM in an understandable way was really nice, and really helps to clarify whats really happening behind the scenes
@TheBitKrieger
@TheBitKrieger 10 ай бұрын
This is a mood point: sure if your method contains only one line then yeah, it will be slower but if you write code like that, you either aren't a good coder or you are writing java ;)
@janoschreppnow3785
@janoschreppnow3785 10 ай бұрын
To be fair to all those C++ code bases littered with virtual functions and interfaces: With C++ not having a clean way to define restrictions (SFINAE my ..) for generic/template parameters until C++20 (and I have not actually seen concepts used in any productive code base to this day..), it was often the easiest way to get at least somewhat sane compiler messages, even though static dispatch or an enum style solution would have been perfectly possible for a lot of these usecases. Rust (btw) does it kinda cleverly by combining dynamic and static dispatch into Traits, although using trait objects is somewhat frowned upon at times.
@totof2893
@totof2893 10 ай бұрын
In C++17 you can use std::variant and std::visit to do static dispatch with clean type like Rust enum. C++20 concept is syntactic sugar for my point of view. Almost everything can be written and read easily with constexpr and static_assert. And SFINAE can be used to test the existence of an expression and transform it into a trait (a la std::is_arithmetic).
@alexsarbu3978
@alexsarbu3978 10 ай бұрын
To be fair you can't really do generic programming in C ;)
@Baptistetriple0
@Baptistetriple0 10 ай бұрын
I don't know why you are saying that trait object are frowned upon, lot and lot of widely used library abuse it, like tokio for exemple. you dive into any async library and you will see Pin everywhere (often aliased by BoxedFuture), it is just so much easier to work with.
@karwszpl5117
@karwszpl5117 10 ай бұрын
Thank you for awesome video!
@jp46614
@jp46614 5 ай бұрын
I've never needed to use inheritance in C++, I can usually get everything done with single level classes
@welehcool2522
@welehcool2522 10 ай бұрын
OOP is just a tool. It will be great if you know how to use it. PBRT use very much OOP a lot. It really difficult when I tried to port their code to Vulkan and GLSL, but then I realized why they choose to use OOP.
@rursus8354
@rursus8354 10 ай бұрын
OOP is the analysis. Using objects is just common sense.
@TheAudioCGMan
@TheAudioCGMan 10 ай бұрын
You can see how atoi is called in the loop to parse the second argument again and again ... I assume that's more operations than the virtual function
@przemeks9121
@przemeks9121 7 ай бұрын
Function with the same signature in a derived class is not virtual by default in a base class as stated somwhere around 3:40. You need virtual keyword in a base class to enable dynamic polymorphism. It's not only for code readability.
@ksbs2036
@ksbs2036 10 ай бұрын
It's the execution pipeline stall/flush/reload that burns the most CPU, not the vtable lookup. However, polymorphism can eliminate huge swaths of complexity in large programs when used appropriately. It's just a tool. Not a panacea
@Nanagos
@Nanagos 10 ай бұрын
I'm reading a book about C++ written by Bjarne Stroustroup (the creator of C++) and he comments this about virtual functions: "The mechanism of calling virtual functions is almost as efficient as calling 'normal' functions (within 25%), so that efficiency questions shouldn't scare anybody off to use a virtual function, where a normal function call is efficient enough."
@zemlidrakona2915
@zemlidrakona2915 10 ай бұрын
The main problem with virtuals is the compiler generally can't inline. That's often a big part of the speed difference. However virtuals are still a good option in many places.
@DogeOfWar
@DogeOfWar 10 ай бұрын
Is there any overhead to using classes without polymorphism in this case? I.e. if you had separate ADD/MUL classes without an Operation virtual execute function does it still perform much worse than the C code you used? I personally think the class structure looks cleaner and is easier to maintain so if you had to compromise somewhere in the middle between flexibility and performance it would be interesting to see if there is a middle ground. Thanks for the vid btw, always enjoy your uploads!
@GabrielGonzalez2
@GabrielGonzalez2 10 ай бұрын
In this specific example, there would still be function call overhead but no virtual function overhead. Functions, Virtual or not, are exactly the same as C functions. You also only have virtual call overhead when you are calling virtually. So like in the C++ example, if you call execute on the Add class directly (without a pointer), there would be no virtual overhead. There's also other cases where the compiler knows the true type of your object at compile time so it can optimize the virtual call away. Also unless you're doing something absolutely performance critical or on super tiny embedded devices, you don't need to worry about virtual call overhead. The actual work you do in your functions will almost always eclipse the overhead of a virtual call where you can barely tell it's there. Remember to avoid premature optimizations, and to use benchmarks to see if you actually need to optimize and where. There is a way to achieve compile-time polymorphism through a technique called "CRTP" although its limited to *only* compile-time polymorphism which this example could work for but I'd have to check and see.
@DogeOfWar
@DogeOfWar 10 ай бұрын
@@GabrielGonzalez2 Thanks!
@MrFlugi
@MrFlugi 10 ай бұрын
this video is an example of "the tool X is bad because I can use it wrong on purpose if I want!" reasoning. No C++ programmer would use polymorphism in a real world scenario like this. The best case scenario I can think of is someone saw an educational code sample which had to fit on a single screen, and thought that the example is the recommended usage, not just the syntax.
@rumisbadforyou9670
@rumisbadforyou9670 10 ай бұрын
What about polymorphism without inheritance / virtual functions? Which is how polymorphism is usually used IRL.
10 ай бұрын
Also good to note the performance impact of branch prediction misses. In normal code, the CPU is trying to predict whether a jump is going to happen, but it always knows the destination of the jump. With virtual functions the CPU knows that we will be jumping but it is trying predict the destination of the jump because the address is loaded from memory. When a branch destination is mispredicted, the CPU first needs to load the instructions for the correct branch, and only then it can start loading any data the instructions might require. The performance is also dependent on how predictable the function usage pattern is.
@JubilantJerry
@JubilantJerry 9 ай бұрын
The real problem is actually the loss of a lot of function inlining opportunities. Even with a switch table, the compiler would be aware of the complete set of behaviors and can inline the function calls. The situation is even better with compile time polymorphism like with CRTP, which often produces the most optimized binary. But with a vtable the compiled binary has to remain compatible even with derived classes made later on that can arbitrarily overload the methods. Devirtualization can sometimes overcome this problem if classes are declared final, but it doesn't work that well and also coders often don't bother marking classes final.
@laenprogrammation
@laenprogrammation 10 ай бұрын
This is the extreme example, your functions are so small that they hardly perform any work. You could even say "OMG THIS IS SO SLOWWWWW, you actually have to do context switching to call your functions that is really slow" This "performance drop" is hardly noticeable when your functions actually perform some work
@mikkelens
@mikkelens 10 ай бұрын
I think the title is misleading: Polymorphism is more than just inheritance. A different type of polymorphism is using generics/having type arguments. In Rust, this complexity is resolved ("monomorphized") during compile time, removing the overhead that inheritance would have in this instance. Of course generics are pretty different to work with than virtual methods, but "polymorphism" was the chosen word. Another example could be using interfaces (dynamic dispatch) and traits, that can also be considered polymorphism and in certain languages (say, rust) also have no runtime overhead. A title that would be less of a problem in my opinion could be "why does inheritance suck for runtime performance?", or "why does inheritance suck?" for short.
@nirajandata
@nirajandata 10 ай бұрын
just 2 hours before this video uploaded, i was watching 1 years old cppcon video on alternative for virtual method
@Mathhead2000
@Mathhead2000 10 ай бұрын
I don't think overridden functions are virtual by default. Unless the specs have changed. If you override without using the virtual keyword, it only uses static analysis to determine with method to run. I.e. no v-table.
@muadrico
@muadrico 10 ай бұрын
Yes. You are right, they are not.
@EUPThatsMe
@EUPThatsMe 10 ай бұрын
"C++ is just a bunch of compiler tricks" - old CS prof. There was a time where C++ was just another pre-processor to a C compiler. Most of the useful parts of C++ can be had in C by using an instance of the "object's" data as the first parameter to each of the "object's" methonds (functions).
@falahati
@falahati 10 ай бұрын
this is purely theorical. in majority of real world programs, the actual function is so much more packed with memory lookups and operations that the single additional pointer dereference's effect on the final performance is insignificant. on the other hand you get a clean and maintainable code. weighing both sides of this scale, my choice personally would be rarely different. in other words, "polymorphism sucks" is very big claim to be fully settled with a 25% performance penalty on a function that has no parameter and does a simple mathematical operation.
@WRXDannyW
@WRXDannyW 10 ай бұрын
This is not saying that C++ is slower than C. It's just that this is a great explanation of why hand coded switch statements can be faster than virtual functions, both of which you can do in C++ also. Virtual calls certainly need to be eliminated from performance critical areas of your code. In this use case I would use templates rather than virtual functions to achieve the same thing with probably better performance than the C code.
@ultimatesoup
@ultimatesoup 9 ай бұрын
A single virtual call is essentially free, it's just a pointer indirection. It's when you have many many calls that you start to see the impact. With modern c++ you can use some template tricks to get rid of it at compile time in most cases
@georgehelyar
@georgehelyar 10 ай бұрын
I once wrote a small C application for a friend using function pointers in structs just to troll them. Good times.
@cdarklock
@cdarklock 10 ай бұрын
"I am building a small project by myself that performs a well-defined function. Here's why it's a Bad Idea to use a language designed for large projects with large teams that will be frequently modified to account for changes in the business landscape, using metrics appropriate to small solo projects of course"
@mwwhited
@mwwhited 9 ай бұрын
In many cases the compile and resolve this if you turn on devirtualization optimizations. You can even provide better hints by finalizing your implementations. And even in non-optimized code the flexibility provided to the developer far out weights performance loss due to virtual table lookups even in extremely tight processing.
@loc4725
@loc4725 10 ай бұрын
Not you but there seems to be a few people on the Internet who either hate OOP or are just obsessed with raw performance, and this is usually marked by the extensive use and emphasis of edge cases. In the real world getting working, maintainable code out of the door quickly is far, far more important. If it takes 0.2 seconds longer to execute then most of the time it simply doesn't matter. Good video though.
@gnagyusa
@gnagyusa 25 күн бұрын
You can do OO in C just fine. You can put function pointers in structs and you can use named struct initializers like this: ``typedef struct { int (*Init)(MyObject* Object); int (*Free)(MyObject* Object); } MyClass;`` MyClass SomeSpecificClass = { .Init= DaConstructor, .Free= DaDestructor };
@nullderef
@nullderef 10 ай бұрын
Does the additional store to memory used in the C++ version also not impact the runtime? That's 2 whole additional writes for the caller and 2 more reads for the callee. Especially on x64 where the calling convention uses registers for integers anyway, so if execute() took the two integers instead the difference between these might be even smaller.
@SimGunther
@SimGunther 10 ай бұрын
The performance tradeoff for obeying "more readability by polymorphism" is just not worth it and there's a better perspective on the problem that can help you use other design patterns instead of polymorphism or switch cases.
@twochilis6763
@twochilis6763 10 ай бұрын
This omits the fundamental advantage of polymorphism, which is that you can call methods on interfaces without knowing all the possible object types, and extend the application without recompiling. For example, you can have code that calls a method, and the object it is called on is provided by a shared library you load at runtime. The implementation may not even be written at the time that you write the code that uses the interface. This flexibility comes at a cost. Which is why code that doesn't provide this flexibility can run faster when that flexibility is not required. But try to provide that same flexibility in C, and you end up copying the same vtable mechanism C++ uses, only you have to do it manually instead of the compiler doing it for you. Polymorphism does not suck. It is a powerful tool, and as such it must be used with care. Learn when to use it, instead of overusing it or swearing it off completely.
@velho6298
@velho6298 10 ай бұрын
I suppose you could always try to emulate how the C++ would implement vtables in C. The indirection will always be more slower compared to enum. edit: try godbolt next time
@kaikalii
@kaikalii 10 ай бұрын
It would have been nice if you had said something about static dispatch and monomorphization, which languages like Rust do by default. For those that don't know, it creates a copy of you code for each type that you pass, so you get all the benefits of polymorphism without this runtime overhead.
@chaqueseconde
@chaqueseconde 2 ай бұрын
The main performance problem with virtual functions is not indirect call but broken inliner pass in compiler. It would be great if author explained in new video what is inlining and devirtualization
@anon_y_mousse
@anon_y_mousse 10 ай бұрын
I've actually written a lot of OO code in C. Depending on how you write it, it can be horrific or reasonably pleasant. When I first wrote my data structures library I implemented it in a somewhat generic way with loads of function pointers and size fields and type ID's in nearly everything. It was really easy to work with, but it kind of looked fugly. At some point I implemented some standard types to use as templates, and it was huge. Not that it ran particularly slow or anything, or produced bloated binaries, but it was a lot to maintain. For the second version I just said "fuck it" and implemented raw data copies and had the user provide a void pointer and a size. Let them figure it out.
@danielmilyutin9914
@danielmilyutin9914 10 ай бұрын
3:40 did you mean function in derived class which have same signature and name as the one declared `virtual` in a base class? In derived class can omit `virtual` keyword but in base it is mandatory. Otherwise, you will overload but not override. Since C++11 best practice is to use keywords `override` and `final` which are written at the end of declaration. Function which does not have `virtual` keyword in base class is common method, not virtual. C++ does not have "virtual by default". And your comparison "fast C code" with "C++ slow code" is actually unfair. C++ developer could write same "C code" if needed. Polymorphysm (or other sorts of type erasure) is a tool which should be applied wisely. Ex. You are sure that two pointer dereferences are neglectably small compared to work done in those virtual functions. And your code is more maintanable with virtual actually. (Ex. end.)
@Mitsunee_
@Mitsunee_ 10 ай бұрын
I find it very interesting how classes in C++ basically work like they used to with babel before JavaScript had its native classes, where classes were transpiled to an implemetation used prototypes and a WeakMap.
@jaysistar2711
@jaysistar2711 10 ай бұрын
In modern C++, one could use a concept for polymorphism, but in Rust switching between a v-table based dynamic polymorphic dispatch and a template based static dispatch is much easier, and with no wrappers.
@muadrico
@muadrico 10 ай бұрын
Yes, I also thought of using CRTP.
@GrantCelley
@GrantCelley 9 ай бұрын
I am a hobbiest programmer specializing in nlp. Classes and python are a godsend. The one thing holding me back usually is getting the code down and polymorphism and computionally heavy(as in needing more compute to compile and interpret) are way better for me because i am the bottleneck more than a computer. Also i cant run c code on google colab.
@TheExtremeCube
@TheExtremeCube 10 ай бұрын
But but, polymorphism is so elegant in some cases
@axelbagi100
@axelbagi100 9 ай бұрын
We can just use templates which can be used for compile time inheritance basically And with the addition of concepts it can be done without gouging your eyes out 😂
@chinoto1
@chinoto1 8 ай бұрын
It's not always a possibility depending on the language and use case, but monomorphization can help a lot.
@grubzer1369
@grubzer1369 10 ай бұрын
I think virtual functions should not be and frequently arent used in hot spots of the code, they are more proper to handle choosing different broad code paths (choosing between exporting to jpg vs png, serial console vs telnet)
@spidermanlift4527
@spidermanlift4527 10 ай бұрын
I use it for my project, although It's used where performance is not critical, so I never knew they were slow.
@psyience3213
@psyience3213 10 ай бұрын
I mean, that's just not what polymorphism is used for. There's no reason why the C code can't be used for the C++. And if you wanted to make it more C++ you'd make the functions templates. Still no need for a class. "This screwdriver is slow at cutting a 2x4" yeah well that's not what a screwdriver is for.
@user-ni9tf5yr6m
@user-ni9tf5yr6m 10 ай бұрын
>it`s important to note that by default functions with the same signature in a derived class are virtual in the parent by default Is it means base class is becoming polymorphic when derived class overrides any its method (have at least one the same method signature) ? OR Derived class just hides base class methods and no vtables are created? I need confirmation 🙂
@tracevandyke2009
@tracevandyke2009 10 ай бұрын
What he said was actually not true. In C++, a base class method cannot be virtual unless it is explicitly marked virtual. A derived class method will only be virtual if it has the same signature as a base class virtual method.
@romsthe
@romsthe 10 ай бұрын
What happens if you declare the function arguments and also the operations themselves as const, which they should be, what happens then when you compile with optimizations ? Could you compare the assembly of both ? C++ might surprise you here ;)
@johnadriaan8561
@johnadriaan8561 9 ай бұрын
While you're right that adding `const` in general has the possibility of improving compiled code, in this contrived example there's no benefit to be had. There's no point in passing the arguments as `const`, since they're passed by value. Declaring the functions themselves as `const` also wouldn't help in this example, where OP is complaining about the indirect virtual call, rather than the compiler's selection of which function to use (which is what `const` helps with). On a more abstract level, it's also (often) a mistake to declare a virtual base function as `const` unless you're trying to establish a guarantee. A derived implementation (which would also have to be declared `const` to override the base class) might want/need to modify its members for some reason. Sure, it could declare those members as `mutable`, but that has its own philosophical problems.
@jurekrasovec
@jurekrasovec 10 ай бұрын
I am tempted to write this in C not C++ as it can be, but as you pointed out, it would be ulgy, so maybe you can do a video on how to write a base class, a class that inherits a base class in C ... and the code looks (well it won't look pretty) at least readable? :D
@junbird
@junbird 10 ай бұрын
Doing inheritence is actually very basic. Let struct Parent { int x; }; you'd define an extension of this type as: struct Child { struct Parent p; int y; }; It's important that struct Parent is the first field of this derived struct Child, as a struct is simply a contigous buffer (exactly like an array). The whole trick is to cast a struct Child pointer to a struct Parent pointer. For example, let void f(struct Parent*); struct Child c = {}; you could call f on c: f((struct Parent*) &c) Now, in struct Parent there could be a function pointer, such as struct Parent { void (*g)(struct Parent*); int x; }; struct Parent::g points to a function which expects a pointer to struct Parent as its only input. If you inizialize struct Parent::g differently for struct Parent and struct Child instances, you could then have a dispatch function such as void g(struct Parent *self) { self->g(self); } g does not provide an actual implementation, which is expected to be contained in whatever self points to, whatever it might be (either a struct Parent or a struct Child). So, let struct Parent p = { .print = printParent, .x = 4 }; struct Child c = { .p = { print = printChild, }; .x = 3 }: with void printParent(struct Parent *p) { printf("I'm a parent and %d", p->x); } void printChild(struct Child *c) { printf("I'm a child and %d %d", c->p->x, c->y); } the output of g(&p); would be "I'm a parent and 4", while the output of g((struct Parent*) &c); would be "I''m a child and 4 3 ". Here you have a function which executes different procedures based on what type of argument you pass to it. This is extremely basic, you can go MUCH deeper, but as you can see this is already ugly enough (and it has no type safety, as I mentioned in a previous comment, you'd need to check for types at runtime, having each instance of each class containing a specific pointer which represent that class, which is what some refer to as the metaclass, you basically render C a dynamically typed language). Believe me, you could write fully featured class-based object oriented code in C (vtables and all), it's just that there's no point to it (if you want an OO C, just go with C++), other than for learning purposes.
@sledgex9
@sledgex9 10 ай бұрын
To be fair, modern (youtube) talks I have watched almost always gravitate towards implementing polymorphism via template metaprogramming techniques, which in turn eliminates runtime dispatching and compile time optimizations, thus achieving max performance.
@BinGanzLieb
@BinGanzLieb 10 ай бұрын
what if using in C an array of function pointers instead of switch-case-statement?
@Alex-qf1pm
@Alex-qf1pm 10 ай бұрын
Do we get the same performance degradation if we use statically allocated 2D arrays? Because I think the vtables are exactly that - they are pre-computed. An interesting comparison would've been creating your own vtable-style arrays in C vs. C++ virtual functions. But yeah, polymorphism is very much overhyped. It does have some nice use cases, but I love the freedoms given by hybrid languages like C++ and python.
@Cjw9000
@Cjw9000 10 ай бұрын
Depends on what you mean by "statically allocated 2D arrays". If you were to embed the full vtable in the structure itself, you would waste a lot more memory, but gain performance because you would only need to look up a single function pointer in the structure itself. If you only store a pointer to the vtable in your structure, it would be exactly the same as in the C++ implementation.
@Alex-qf1pm
@Alex-qf1pm 10 ай бұрын
@@Cjw9000 yes, I meant a pointer to the vtable. That's why it would be an interesting comparison :-)
@Spartan322
@Spartan322 10 ай бұрын
You need to keep in mind that historically C++ vtables were better on older systems then they are now, modern systems have made it harder to justify their current existence except when the inner elements of your virtual table functions is the hotpath. Also final pretty much makes them almost as fast as the function call because they get "devirtualized" by the compiler.
@mr.togrul--9383
@mr.togrul--9383 10 ай бұрын
First (it is very painful not to post this when u see no comments)
@LowLevelLearning
@LowLevelLearning 10 ай бұрын
gottem
@picblick
@picblick 10 ай бұрын
"Why did you write it?" "It's the law!"
@jolynele2587
@jolynele2587 10 ай бұрын
my condolences
@mrx10001
@mrx10001 10 ай бұрын
wouldn't it be faster if the compiler just auto converted the function calls into an enum based check that directly calls the function? Or would the casting still be equivalent to a x20 perf loss?
@ambuj.k
@ambuj.k 10 ай бұрын
Hey, I really love your videos but from one vim user to another, what is your colorscheme?
@piguyalamode164
@piguyalamode164 10 ай бұрын
also, this is why monomorphization is the default behavior in rust
@LewiLewi52
@LewiLewi52 10 ай бұрын
This video explains polymorphism quite well. I just do not like how it paints it as a massive performance hit. It is just a tool to be used when appropriate, yes it can be overused but so can any tool. When performance becomes a problem i can guarentee that it is probably not polymorphism eating your processing time.
@JurekOK
@JurekOK 10 ай бұрын
soo . . . in C, you can do object-oriented programming, you merely do not get the syntax sugar.
@microgut9104
@microgut9104 10 ай бұрын
dude...please tell me what font that is ur using in that neovim editor...i have been looking for it online cant find it...please I need help
are "smart pointers" actually smart?
9:44
Low Level Learning
Рет қаралды 69 М.
#55 Polymorphism in Java
3:55
Telusko
Рет қаралды 99 М.
КАХА и Джин 2
00:36
К-Media
Рет қаралды 4,1 МЛН
CAN YOU HELP ME? (ROAD TO 100 MLN!) #shorts
00:26
PANDA BOI
Рет қаралды 36 МЛН
Be kind🤝
00:22
ISSEI / いっせい
Рет қаралды 21 МЛН
Eccentric clown jack #short #angel #clown
00:33
Super Beauty team
Рет қаралды 25 МЛН
Dynamic Binding (Polymorphism) With The Virtual Keyword | C++ Tutorial
9:57
everything is open source if you can reverse engineer (try it RIGHT NOW!)
13:56
Low Level Learning
Рет қаралды 1,2 МЛН
The Flaws of Inheritance
10:01
CodeAesthetic
Рет қаралды 884 М.
PHP is Wack. (Coding in a Random Language Every Day)
14:41
Low Level Learning
Рет қаралды 54 М.
Reacting to Controversial Opinions of Software Engineers
9:18
Fireship
Рет қаралды 2 МЛН
Object-Oriented Programming is Bad
44:35
Brian Will
Рет қаралды 2,3 МЛН
Why i think C++ is better than rust
32:48
ThePrimeTime
Рет қаралды 267 М.
8 Design Patterns | Prime Reacts
22:10
ThePrimeTime
Рет қаралды 380 М.
Power up all cell phones.
0:17
JL FUNNY SHORTS
Рет қаралды 49 МЛН
Will the battery emit smoke if it rotates rapidly?
0:11
Meaningful Cartoons 183
Рет қаралды 1,1 МЛН
Карточка Зарядка 📱 ( @ArshSoni )
0:23
EpicShortsRussia
Рет қаралды 354 М.
Apple, как вас уделал Тюменский бренд CaseGuru? Конец удивил #caseguru #кейсгуру #наушники
0:54
CaseGuru / Наушники / Пылесосы / Смарт-часы /
Рет қаралды 4,5 МЛН