The requirement looks wrong. The reason is that virtual methods and copy constructors don't work together. Virtual methods are useful if you use the object polymorphically, that is via reference/pointer to a base class. But copy constructor always constructs object of the static type it is written as, so if you give it polymorphic object, it will only create the base class, not the subclass you wanted. This is called slicing.
So you want to either:
Have a templated framework that knows the type at compile time and uses copy-constructor. But this does not need any virtual methods, nor for that matter a common base type, since the code will be compiled with the specific template parameter. Compiler will complain if the type substituted does not support the operations the template tries to use, but you can slightly improve diagnostics by using some explicit checks, e.g. from Boost.Concept Check.
Have a framework using polymorphic objects with base class, but that can't create copies with copy constructor, because it does not know the type to construct. There are three ways out:
- Have a pure virtual
Base *clone()
method, that will be implemented to do return new Derived(this)
in each concrete subclass. You should probably wrap that in unique_ptr
or shared_ptr
immediately to avoid leaking the copies.
- Instead of copies, hand out references or smart pointers, probably
shared_ptr
in this case. It would probably be either const references/smart pointers or typed with interface that only contains methods that can't affect invariants in the framework.
- Make the method that inserts the objects a template, that would construct a cloner and store it along with the object. Cloner is a function
template<typename T> Base *clone(const Base *obj) { return new T(dynamic_cast<const T &>(*obj); }
(you can have a functor or a class with multiple helper methods like this). To ensure you really have correct type, the insert method should probably wrap the call to new
as well similar to e.g. make_shared
.
By the way, nothing of this has anything to do with rule of three. Because rule of three says that if default copy constructor, default assignment operator or default destructor are not good enough for the class, than none of them is. But not because compiler would require it, but because the logic probably does. But for most classes that want to be copyable they are good enough. Because in most cases you hide the resource handling in some smart pointer and just let the default copy/assignment/destructor call to it.
In your first scenario, the behavior is defined. You have a pointer to X and you destroy an X via that pointer. The fact that X derived from B doesn't cause a problem.
The second scenario is where the big problem arises. Here you have undefined behavior, because you're destroying a derived object via a pointer to the base object. In such a case you must have a virtual dtor to get defined behavior.
The exact result of doing this varies. In some cases, the program can crash. In others, the base dtor is invoked but the derived dtor isn't, so the object is partially (but not completely) destroyed. I did give some concrete examples for one example, in an answer on SO. That's just one particular case though--changing the compiler or code could change the result completely.
At least in my opinion, attempting to eliminate dynamic allocation isn't really a cure. For example, consider code like this:
#include <iostream>
struct base {
virtual void show() { std::cout << "type: base\n"; }
};
struct derived : public base {
derived operator+(int) { return derived(); }
virtual void show() { std::cout << "type: derived\n"; }
};
void f(derived &d) {
base &bb{ d+1 };
bb.show();
// boom!
}
int main() {
derived d;
f(d);
}
When we invoke bb.show()
, it (as expected) shows that the type is derived
. That is, we have a reference to base
bound to a temporary object of type derived
. At the end of the function (marked "boom!") that temporary object of type derived
is destroyed via a reference to base
. We haven't used any dynamic (or static) allocation, but we still have undefined behavior.
This is probably why David Rodriguez (who's a smart guy and knows C++ well--continue paying attention to what he says) asked what he did--at least for the situation under discussion, preventing dynamic allocation doesn't prevent problems.
Best Answer
Non-virtual functions are called based on the type that the compiler sees. If you have a Base* variable, then calling non-virtual functions will call the Base functions. That's the difference between virtual and non-virtual; calling a virtual function always calls the function that is appropriate for the object.
And you are not assigning an object. You are assigning an object pointer. If you assigned an object (for which you would have to implement an assignment operator), then both virtual and non-virtual would call the Base function, because the object would be a Base object.