4 ms·
How do you fell about the pitfalls, like "if you inherit from a class, make sure the base destructor is virtual, or you just added a memory leak" Some books on
by a3w 4y ago
How do you fell about the pitfalls, like "if you inherit from a class, make sure the base destructor is virtual, or you just added a memory leak"
Some books on C++ teach you those, but I give up very early on learning languages with non-obvious expectations that offers few safeguards.
- Chabsff 4y agoThat's not even particularly good advice, which just goes to show how subtle the pitfalls are. The rule really should be "If it is at all possible to delete instances from a pointer to the base class, then you need a virtual destructor". The overhead of adding a virtual destructor to a base class with no other virtual functions is far from negligible in many cases, so the distinction is meaningful (though modern guidance tends discourage non-polymorphic inheritance in the first place anyways). The same thing goes for the good-old "Do not use raw pointers." The rule is actually: "Do not use raw pointers with implicit ownership semantics attached to them."
- nyanpasu64 4y agoIn retrospect I wish C++ used fat pointers rather than inheritance for virtual dispatch, like Rust but where interfaces/traits can have fields (Rust traits don't have fields and are less powerful than C++ base classes). This prevents accidentally overriding a base class method (eg. QWidget) by declaring a subclass method of the same name, eliminates the need to add a vtable pointer to object instances which you never cast to an interface pointer, and eliminates the need to juggle pointer offsets when performing multiple inheritance method calls, which can cause UB when performing C-style pointer casts and makes decompiling the code a nightmare (possibly fields in traits will add this requirement again, I'm not sure).
- rramadass 4y agoThat is because C++ is not taught properly. It is not just a collection of syntactical features but a multi-paradigm language (i.e. supporting Procedural, Object-Oriented, Generic and Compile-time) where a feature is best suited to a particular paradigm and can also be used to express different concepts. Once you grasp this, things become quite clear and you understand how to design in C++. To take your example; the same C++ "class" syntax can be used to express different concepts; a) A user-defined value type eg. ComplexNumber. Therefore the dtor need not be virtual since the class is not designed for inheritance. b) A pure interface used for "Interface Inheritance" i.e. all methods are "pure virtual". Therefore you need a pure virtual dtor. Java has the "Interface" keyword for this. c) A class designed for a type hierarchy used for "Implementation Inheritance". Therefore you need a virtual dtor. d) A class can also be used as a simple namespace eg. all static members; not designed for inheritance and thus no dtor needed.
- bogomipz 4y ago>"That is because C++ is not taught properly. It is not just a collection of syntactical features but a multi-paradigm language (i.e. supporting Procedural, Object-Oriented, Generic and Compile-time) where a feature is best suited to a particular paradigm and can also be used to express different concepts. Once you grasp this, things become quite clear and you understand how to design in C++." Might you or someone else recommend a resource that does explain which features are suited for each the programming paradigms C++ support? Is there a modern resource that does teach it correctly like this? Knowing what features are relevant to your paradigm seems like a good way to approach learning C++.
- rramadass 4y agoC++ actually has two axes; 1) Paradigm axis (i.e. Procedural, Object-Oriented, Generic, Compile-time and Functional) and 2) Usage axis (i.e. Low-level bare-metal, Kernel, Library, Application). Every feature has an ideal application at the intersection of these two axes. Another way to look at it is how best to map concepts from the Problem-domain directly onto language features. Here techniques like Commonality/Variability analysis (bedrock of DDD, Patterns etc.) are used to express concepts using specific language features and then combine those into a coherent design. Most of the above design ideas are found only in pre Modern C++ books. I don't know of any newer books which actually teach multi-paradigm design using Modern C++. So you will have to read the older books and then think about how you would map the same ideas into Modern C++. You might find the following useful; Pre Modern C++: 1) Multi-Paradigm Design for C++ by James Coplien. 2) Scientific and Engineering C++: An Introduction With Advanced Techniques and Examples by John Barton & Lee Nackman. 3) Advanced C++ Programming Styles and Idioms by James Coplien. 4) Modern C++ Design: Generic Programming and Design Patterns Applied by Andrei Alexandrescu. Modern C++: 1) Functional Programming in C++: How to improve your C++ programs using functional techniques by Ivan Cukic.
- bogomipz 4y agoThank you for the wonderfully insightful and detailed response. This is really helpful. Cheers!
- tejohnso 4y agoYou'd want to read more than one book. And in my opinion you also need to digest the C++ Core guidelines and also use something like clang-tidy with high sensitivity, and when issues are flagged go dig into the details. But then you realize it's just all too much, and you're looking for something less complex again. I wonder how useful a C++ that I could understand in its entirety would be. But I don't understand english in its entirety either. I use what I know and get by just fine.
- ncmncm 4y agoInheritance is a specialist technique, use of virtual members moreso. Most destructors in modern C++ are left to the compiler to generate. It has been years since I last coded a virtual destructor.
- kazinator 4y agoSpeaking of inheritance, how do you feel about this not requiring any diagnostics. It produces a weird result without containing any cast notation to subvert the type system. $ g++ -Wall -W -Wextra deriv.cc -o deriv $ ./deriv 1.08654e-10 $ cat deriv.cc #include <cstdio> struct Base { public: double y; Base() : y(1.0) {} }; struct Derived : public Base { public: int a, b; Derived() : a(0x3DDDDDDD), b(0x3DDDDDDD) {} }; int main() { Base *b = new Derived[5]; b++; std::printf("%g\n", b->y); }
- jahnu 4y agoFor me this is the least of c++’s problems. Today I wouldn’t let that past a review as it should be using a std::array. If a c++ dev has a reason to drop down to this unsafe code they should know what the consequences are.
- deleted 4y ago[deleted]
- gpderetta 4y agoThis is bad, but it is really no different than: int * b = new int; *++b; Dereferencing past the end is always UB in C and C++ and it is hard to fix at the language level when using raw pointers.