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."
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.
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++.
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.
For learning Modern C++, you might find these recommendations useful: https://news.ycombinator.com/item?id=32884432
$ 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);
} 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.It has been years since I last coded a virtual destructor.
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.
Some books on C++ teach you those, but I give up very early on learning languages with non-obvious expectations that offers few safeguards.