First of all, the article is not completely correct. dynamic_cast checks the type of an object and may fail, static_cast does not check and largely requires the programmer to know what they're doing (though it will issue compile errors for some egregious mistakes), but they may both be used in polymorphic situations. (dynamic_cast has the additional requirement that at least one of the involved types has a virtual method.)
Polymorphism in C++, in a nutshell, is using objects through a separately-defined interface. That interface is the base class, and it is almost always only useful to do this when it has virtual methods.
However, it's rare-but-possible to have polymorphism without any virtual methods; often this is a sign of either bad design or having to meet external requirements, and because of that, there's no way to give a good example that will fit here. ("You'll know when to use it when you see it," is, unfortunately, the best advice I can give you here.)
Polymorphism example:
struct Animal {
virtual ~Animal() {}
virtual void speak() = 0;
};
struct Cat : Animal {
virtual void speak() { std::cout << "meow\n"; }
};
struct Dog : Animal {
virtual void speak() { std::cout << "wouf\n"; }
};
struct Programmer : Animal {
virtual void speak() {
std::clog << "I refuse to participate in this trite example.\n";
}
};
Exercising the above classes slightly—also see my generic factory example:
std::auto_ptr<Animal> new_animal(std::string const& name) {
if (name == "cat") return std::auto_ptr<Animal>(new Cat());
if (name == "dog") return std::auto_ptr<Animal>(new Dog());
if (name == "human") return std::auto_ptr<Animal>(new Programmer());
throw std::logic_error("unknown animal type");
}
int main(int argc, char** argv) try {
std::auto_ptr<Animal> p = new_animal(argc > 1 ? argv[1] : "human");
p->speak();
return 0;
}
catch (std::exception& e) {
std::clog << "error: " << e.what() << std::endl;
return 1;
}
It's also possible to use polymorphism without inheritance, as it's really a design technique or style. (I refuse to use the buzzword pattern here... :P)
- Yes, they are the same. The derived class not declaring something virtual does not stop it from being virtual. There is, in fact, no way to stop any method (destructor included) from being virtual in a derived class if it was virtual in a base class. In >=C++11 you can use
final
to prevent it from being overridden in derived classes, but that doesn't prevent it from being virtual.
- Yes, a destructor in a derived class can be omitted if it has nothing to do. And it doesn't matter whether or not its virtual.
- I would omit it if possible. And I always use either the
virtual
keyword or override
for virtual functions in derived classes for reasons of clarity. People shouldn't have to go all the way up the inheritance hierarchy to figure out that a function is virtual. Additionally, if your class is copyable or movable without having to declare your own copy or move constructors, declaring a destructor of any kind (even if you define it as default
) will force you to declare the copy and move constructors and assignment operators if you want them as the compiler will no longer put them in for you.
As a small point for item 3. It has been pointed out in comments that if a destructor is undeclared the compiler generates a default one (that is still virtual). And that default one is an inline function.
Inline functions potentially expose more of your program to changes in other parts of your program and make binary compatibility for shared libraries tricky. Also, the increased coupling can result in a lot of recompilation in the face of certain kinds of changes. For example, if you decide you really do want an implementation for your virtual destructor then every piece of code that called it will need to be recompiled. Whereas if you had declared it in the class body and then defined it empty in a .cpp
file you would be fine changing it without recompiling.
My personal choice would still be to omit it when possible. In my opinion it clutters up the code, and the compiler can sometimes do slightly more efficient things with a default implementation over an empty one. But there are constraints you may be under that make that a poor choice.
Best Solution
Suppose you created a derived class, then replaced every instance of the base class in your program with that derived class. Did the resulting program behave the same? If not, then you have introduced some gotchas. These gotchas will probably be accounted for in the case of a class designed for inheritance (e.g. I've seen some classes designed for inheritance that actually have functions to check if other functions are implemented to allow for the case of them not even existing, e.g. file i/o classes that might be missing seek functionality). Since an abstract class is always going to have been designed with inheritance in mind but a concrete class might not, this could be unsafe.
The explanation above talks about the case where the resulting derived class is not fully compatible with the base class, which is dangerous. But what if it is fully compatible? In this case, some of the potential justifications for using inheritance (instead of composition) fall apart. We don't have any virtual functions or protected members (if we do, then I assume the class was designed for inheritance). Still, using inheritance in this case is probably pretty safe, though ensuring it really is fully compatible is tough. I'd rather use composition and be safe.
Edit:
The term for what I was talking about above is the Liskov Substitution Principle. A shorter explanation would be that in most cases, a derived class of a class not designed for inheritance will either violate Liskov's Substitution Principle or will not be able to accomplish anything useful that could not have been accomplished using composition instead.