C++ Weekly - Ep 154 - One Simple Trick For Reducing Code Bloat

Sdílet
Vložit
  • čas přidán 10. 02. 2019
  • ☟☟ Awesome T-Shirts! Sponsors! Books! ☟☟
    Upcoming Workshop: Understanding Object Lifetime, C++ On Sea, July 2, 2024
    ► cpponsea.uk/2024/sessions/und...
    Upcoming Workshop: C++ Best Practices, NDC TechTown, Sept 9-10, 2024
    ► ndctechtown.com/workshops/c-b...
    T-SHIRTS AVAILABLE!
    ► The best C++ T-Shirts anywhere! my-store-d16a2f.creator-sprin...
    WANT MORE JASON?
    ► My Training Classes: emptycrate.com/training.html
    ► Follow me on twitter: / lefticus
    SUPPORT THE CHANNEL
    ► Patreon: / lefticus
    ► Github Sponsors: github.com/sponsors/lefticus
    ► Paypal Donation: www.paypal.com/donate/?hosted...
    GET INVOLVED
    ► Video Idea List: github.com/lefticus/cpp_weekl...
    JASON'S BOOKS
    ► C++23 Best Practices
    Leanpub Ebook: leanpub.com/cpp23_best_practi...
    ► C++ Best Practices
    Amazon Paperback: amzn.to/3wpAU3Z
    Leanpub Ebook: leanpub.com/cppbestpractices
    JASON'S PUZZLE BOOKS
    ► Object Lifetime Puzzlers Book 1
    Amazon Paperback: amzn.to/3g6Ervj
    Leanpub Ebook: leanpub.com/objectlifetimepuz...
    ► Object Lifetime Puzzlers Book 2
    Amazon Paperback: amzn.to/3whdUDU
    Leanpub Ebook: leanpub.com/objectlifetimepuz...
    ► Object Lifetime Puzzlers Book 3
    Leanpub Ebook: leanpub.com/objectlifetimepuz...
    ► Copy and Reference Puzzlers Book 1
    Amazon Paperback: amzn.to/3g7ZVb9
    Leanpub Ebook: leanpub.com/copyandreferencep...
    ► Copy and Reference Puzzlers Book 2
    Amazon Paperback: amzn.to/3X1LOIx
    Leanpub Ebook: leanpub.com/copyandreferencep...
    ► Copy and Reference Puzzlers Book 3
    Leanpub Ebook: leanpub.com/copyandreferencep...
    ► OpCode Puzzlers Book 1
    Amazon Paperback: amzn.to/3KCNJg6
    Leanpub Ebook: leanpub.com/opcodepuzzlers_book1
    RECOMMENDED BOOKS
    ► Bjarne Stroustrup's A Tour of C++ (now with C++20/23!): amzn.to/3X4Wypr
    AWESOME PROJECTS
    ► The C++ Starter Project - Gets you started with Best Practices Quickly - github.com/cpp-best-practices...
    ► C++ Best Practices Forkable Coding Standards - github.com/cpp-best-practices...
    O'Reilly VIDEOS
    ► Inheritance and Polymorphism in C++ - www.oreilly.com/library/view/...
    ► Learning C++ Best Practices - www.oreilly.com/library/view/...
    ChaiScript: chaiscript.com
  • Věda a technologie

Komentáře • 75

  • @risajajr
    @risajajr Před 4 lety +33

    It's 9 months since this video was posted and I just tried the different examples using Compiler Explorer. The results were quite different from what was shown here. Generally, x-84-64 gcc (trunk) was much better that clang trunk at optimizing the code in all cases. Declaring a destructor added 3 instructions vs. not declaring one (26 vs 29. For x86-64 clang (trunk), declaring a destructor actually reduced code by 7 instructions vs not declaring one (83 vs 90). This is with using std::string and two emplace_back calls. With the destructor also defined, gcc produces 40 lines vs clang's 106.
    Declaring or not declaring a destructor had varying impacts, depending on the data types being used. For int, gcc generated 10 lines fewer with a destructor declared vs not (26 vs 36). Clang is the other way around. W/o a destructor declared, it generates 28 lines. With it declared, it generates 71 lines.
    It's difficult to make a pat rule about whether or not to declare a destructor. Thus far, though, also defining one will add extra instructions. But the choice of compiler seems to be far more important than the declare/don't declare of the destructor.
    BTW, ~S() = default produces the same instruction count as not declaring ~S() at all.

  • @mikecmw8492
    @mikecmw8492 Před 5 lety +38

    Funny where I used to work all the cpp gurus demanded all objects have destructors regardless of use

  • @xonxt
    @xonxt Před 4 lety +28

    Visual Studio, when adding a new class, automatically creates an empty constructor and destructor in the .cpp file and I might sometimes forget to delete it. Thanks for the tip.

    • @superscatboy
      @superscatboy Před 2 lety +3

      Weird, it never does that for me. Maybe there's a way to turn that behaviour off?

  • @danielphd5072
    @danielphd5072 Před 5 lety +3

    Thank you Jason Turner

  • @hopefulrational9855
    @hopefulrational9855 Před 2 lety

    wow.. i actually tried this on our company's codebase a few weeks ago. It really really made a difference in the size of executable. Very true.

  • @KobiCohenArazi
    @KobiCohenArazi Před 5 lety +3

    Dude, if I had a penny every time I pointed this out in a code review...

  • @DamianReloaded
    @DamianReloaded Před 5 lety +1

    Great tip! Maybe removing an empty destructor or even a trivial one should probably qualify as a compiler optimization?

  • @ruadeil_zabelin
    @ruadeil_zabelin Před 5 lety +4

    But what about having default move ctor and move assign? Then it's all movable again and then it shouldn't be a problem? Or is that still a problem? I make a habbit of providing at least 1 ctor (could be default), copy +move ctor (default or delete), copy+move operator (default or delete) and 1 dtor (pretty much always default)... Would I still run into this? Because if I allow for default move it should be able to figure this out right?

  • @michal.gawron
    @michal.gawron Před 5 lety +3

    6:35 I've seen this exact pattern in a code written by someone who claimed that compiler can't be as fast with "smart" pointer as with raw pointer. He implemented the "smart" himself, of course, and added the nulling in the d-tor. ;-) Why???///

  • @timhaines3877
    @timhaines3877 Před 5 lety +2

    That Heap Allocation eLlision Optimization (HALO) is really nice in situations like this. It's something I hope the gcc devs give serious consideration to.

  • @adriangrzemski
    @adriangrzemski Před 5 lety +33

    what about ~() = default; ?

    • @austinmccartney1922
      @austinmccartney1922 Před 5 lety +1

      Doesn't that implicitly delete the move ctor/assignment?

    • @cicciobombo7496
      @cicciobombo7496 Před 5 lety

      It disables it anyway stackoverflow.com/questions/33957037/does-a-default-virtual-destructor-prevent-compiler-generated-move-operations

    • @TheParkourPenguin
      @TheParkourPenguin Před 5 lety +2

      That's fine as long as the destructor is explicitly-defaulted at its first declaration. If you do it later, it's considered user-provided and therefore no longer trivial.
      Also, the destructor can't be virtual, the class's direct base classes must have trivial destructors, and the class's non-static class-type data members must have trivial destructors. The person in the stackoverflow answer doesn't know what they're talking about, and the code in the original question violates the virtual requirement for a trivial destructor.
      References:
      eel.is/c++draft/class.dtor#6
      eel.is/c++draft/dcl.fct.def.default#5

    • @climatechangedoesntbargain9140
      @climatechangedoesntbargain9140 Před 5 lety

      @r00x its not always pointless, e.g. if you have forward declarations

  • @kmhofmann
    @kmhofmann Před 5 lety +16

    Thanks for the video! I'm wondering why there is a difference in generated code between having no constructor, and having a constructor declared as `~S() = default;`. Shouldn't these cases be more or less equivalent?

    • @asuasuasu
      @asuasuasu Před 5 lety +1

      `~S() = default;` seems to be redundant for me, but I could be wrong. In my understanding, `= default;` simply is used to "reactivate" default definitions implicitly deleted otherwise and don't really change their behavior otherwise. I could be wrong though, I know this part of C++ a little less.

    • @vladalex9556
      @vladalex9556 Před 5 lety +2

      it seems to have the seme effect as ~S(){}

    • @kmhofmann
      @kmhofmann Před 5 lety +4

      @slipcurve The example in Jason's video: godbolt.org/z/Kpxsu4; this emits 231 lines of assembly without, and 328 lines with `~S() = default;`, using x86-64 clang (trunk). Similar behavior with GCC.

    • @jafaruruc
      @jafaruruc Před 5 lety +1

      @@kmhofmann Defaulting the destructor for the int case doesn't change the generated code (godbolt.org/z/YrpXo7 ), but doing so for the string case generate the same code as inlining the destructor (godbolt.org/z/UYl3xp )

  • @JohnDlugosz
    @JohnDlugosz Před 2 lety +2

    What if you use =default for the destructor?

  • @jonesconrad1
    @jonesconrad1 Před 5 lety +1

    I haven't watched it yet but the title caught my eye. I need a welcome break from debugging a compile error in a generic lambda passed to std::visit inside a SFINAE'd template function inside a templated class.

  • @connorhorman
    @connorhorman Před 5 lety +2

    dtors are always noexcept unless you make them noexcept(false) or the implicitly generated one is noexcept. Hense why I assume dtors are always noexcept

  • @SrIgort
    @SrIgort Před 2 lety +1

    About 7:14, aren't destructors already implicitly noexcept?

  • @theIpatix
    @theIpatix Před 5 lety +10

    Is there a way to omit the empty destructor in case of polymorphic objects? Perhaps I'm not remembering it right, but if I do so I remember that I always had to explicitly declare a virtual destructor, even if it was empty.
    PS: Aren't destructors noexcept by default?

    • @Vasily.Vasilyev
      @Vasily.Vasilyev Před 5 lety +4

      Often you can find clear and comprehensive explanation at cppreference:
      "As with any implicitly-declared special member function, the exception specification of the implicitly-declared destructor is non-throwing unless the destructor of any potentially-constructed base or member is potentially-throwing. In practice, implicit destructors are noexcept unless the class is "poisoned" by a base or member whose destructor is noexcept(false)."
      Source: en.cppreference.com/w/cpp/language/destructor

    • @zakharbondia1647
      @zakharbondia1647 Před 5 lety

      I guess, the only way ~() = default. Default generated destructors are noexcept only if all members' destructors are noexcept. Custom destructors shall explicitly be marked noexcept if they are

    • @theIpatix
      @theIpatix Před 5 lety

      @@Vasily.Vasilyev Thanks!

  • @BloodHaZaRd666
    @BloodHaZaRd666 Před 5 lety +3

    so base on the example; this doesnt take account cases of virtual destructors and heritance. right ?

    • @stephenhowe4107
      @stephenhowe4107 Před 4 lety +2

      Makes no difference.
      If you are writing a base class, it must be virtual, say so and declare virtual ~Base() = default; but only if the compiler generated destructor does the right thing.
      If you are writing a derived class, it will be virtual if the base destructor is virtual but you can say it is virtual anyway, say so and declare virtual ~Derived() = default; but only if the compiler generated destructor does the right thing.
      And the same for virtual inheritance.
      The key thing is will the compiler-generated destructor do the right thing (part of rule of 0). If so, let the compiler generate it for you.
      Jason's advice generalises to default constructors, copy constructors, assignment operators, move constructors and , move assignment operators. If the compiler is not going genrate the wrong thing, then let it go ahead.

  • @theugobosschannel8466
    @theugobosschannel8466 Před 2 lety +1

    I have plenty of empty destructors! Lol

  • @petermuller9518
    @petermuller9518 Před 5 lety +3

    As others have already commented, I also use empty destructors for abstract classes. What's the best way to handle this?

  • @ernestuz
    @ernestuz Před 3 lety +1

    GCC seems to be optimizing for speed, clang looks like optimizing for size. Have you tried to use -Os for GCC?

  • @losthighway4840
    @losthighway4840 Před rokem

    Just what I think of when someone says "code bloat". Some generated compiler assembly from destructors /s

  • @real998877
    @real998877 Před 4 lety +1

    With -O2 optimization
    struct S {
    std::string s;
    };
    and
    struct S {
    std::string s;
    ~S() {}
    };
    do exactly the same thing and lead to exactly the same object code. It's possible that with no optimization or a bad compiler the one with the explicit destructor takes more microprocessor instructions, but any decent compiler can figure out the two versions are identical.

    • @YourCRTube
      @YourCRTube Před 3 lety +1

      Inside a vector? Inside of vector, inside a class, inside a vector? You see, at some point the optimizer will give up and this is why it is important to have quality code in the first place and enjoy the optimizer only as a bonus, not count on it to write you the desired program. And as aside, there are environments that work in debug 99.9% of the time.

  • @orielcochavi
    @orielcochavi Před 5 lety

    What about dangling pointer? (In your pointer example)
    en.m.wikipedia.org/wiki/Dangling_pointer

  • @ruadeil_zabelin
    @ruadeil_zabelin Před 3 lety

    What if I always define everything? I define all my copy and move operators properly (=default, =delete on copy sometimes) and a default dtor. All in the header. Would that be any different?

    • @OMGclueless
      @OMGclueless Před 3 lety +1

      It's not a good idea. It means your classes will never be trivial even if they could otherwise be, which disables a number of optimizations in the compiler and in the standard library. And there are lots of pitfalls you can make (for example, are you correctly labeling all of your move constructors as noexcept whenever possible? if not, then vector operations will be much more expensive than they otherwise could be because the vector must use the copy constructor when resizing).

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

    This is such a good clickbait title

  • @lincolnsand5127
    @lincolnsand5127 Před 4 lety

    What if I did `~S() = default;`?

  • @multiHappyHacker
    @multiHappyHacker Před 2 lety

    Am I defining a destructor when I do ~ClassName() = default; ?

    • @cppweekly
      @cppweekly  Před 2 lety +1

      You are not defining a destructor, but you've signaled to the compiler that you want to do something special with the other special member functions and it does affect code generation.
      play with this example:
      compiler-explorer.com/z/887q9xd1T

  • @tadjekivanov3908
    @tadjekivanov3908 Před 5 lety +1

    Hello and thank you for your job!
    At the end of this episode you told about PIMPL idiom and about the rule of 5. I've been playing around with the compiler with this idiome based on unique_pointer and as a result did not watch any overhead in generated code. So why should we write more code than required making it harder to read?
    Thank you!
    Here is my code example: godbolt.org/z/BxU4Jw
    You can play it by comment out the `#define optimize`. Also you can watch how it works for both: creating an object and emplacing it into the vector (Need to uncomment also move cons).

  • @orocimarosay1447
    @orocimarosay1447 Před 3 lety

    why make an useles destructor in the first place tho

  • @ggoedert
    @ggoedert Před 5 lety +1

    Isn't this just the case of transforming a POD struct into a non-POD?

  • @psymapsyma4527
    @psymapsyma4527 Před 5 lety

    Help! What editor is that? How to see the assembly code on your c++ code. Im a newbie i want to learn assembly. Thanks.

  • @Embedonix
    @Embedonix Před 5 lety

    wow FML

  • @fmdj
    @fmdj Před 2 lety

    I think I might have written like 3 destructors in my life, not feeling too concerned here

  • @xarcaz
    @xarcaz Před 3 lety

    I was expecting "Don't use std::embed on a selfie."

  • @brianwest7344
    @brianwest7344 Před 3 lety

    why no downvotes, how can I know potential bad advice

  • @RaaynML
    @RaaynML Před 5 lety +1

    Reducing a assembly bloat, this isn't going to help the source code bloat

    • @notthedroidsyourelookingfo4026
      @notthedroidsyourelookingfo4026 Před 3 lety +1

      Well, when you're removing ASM bloat by removing source code, it kinda is...

    • @YourCRTube
      @YourCRTube Před 3 lety +5

      Code bloat as a term in general refers to code size in the binary - the assembly bloat.

  • @cavesalamander6308
    @cavesalamander6308 Před 2 lety

    Noexcept destructor? .... Hmm... If the destructor will throw exceptions, the program will be ill-formed ...

    • @cppweekly
      @cppweekly  Před 2 lety

      You are allowed to throw an exception from a destructor if you really want to. It's not ill-formed.
      stackoverflow.com/questions/41174167/c-exception-in-destructor

    • @cavesalamander6308
      @cavesalamander6308 Před 2 lety

      @@cppweekly OK, perhaps "ill-formed" is not correct term here.
      But what about
      1) finishing the destructor (freeing object's memory)?
      2) execution of the base class destructor?
      AFAIUI (may be incorrectly) problems are so hard that program termination is really the only correct solution. :-)

    • @cavesalamander6308
      @cavesalamander6308 Před 2 lety

      @@cppweekly OK, perhaps "ill-formed" is not correct term here.
      But what about
      1) finishing the destructor (freeing object's memory)?
      2) execution of the base class destructor?
      AFAIUI (may be incorrectly) problems are so hard that program termination is really the only correct solution. :-)

  • @vladalex9556
    @vladalex9556 Před 5 lety +2

    wtf yould i have a destructor that does nothing :))) it is just a waste of space and looks ugly

    • @babgab
      @babgab Před 3 lety

      Desperation tactic for catching use-after-free and memory corruption bugs.

  • @perfectionbox
    @perfectionbox Před 3 lety +1

    these useless dtors sound like something a future compiler will optimize away

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

    I usually set default constructor, copy, move, copy assign, move assign, and destructor all to default. Is that also bad?

    • @cppweekly
      @cppweekly  Před 26 dny

      That's probably technically OK, but why? You're telling the compiler to do what it already wanted to do already if you hadn't told it to do anything.

    • @damnstupidoldidiot8776
      @damnstupidoldidiot8776 Před 26 dny

      @@cppweekly Mostly, there is a code template (not C++ template) that I can summon in with the defaulted special member functions. That way, if I need them, I don't have to type out their function prototypes, only the body, if I don't, they stay as defaulted, it is for convenience.