Firstly, the "trick" is possible with unique_ptr
. You simply have to provide a type-erasing deleter, as below.
However if you look at the implementation you will of course see that it involves a call through a function pointer - which is exactly what a virtual destructor does under the covers.
virtual function calls are not at all expensive. They simply involve one more memory fetch. If you're doing this in a tight loop, which is the only time performance is ever an issue, that fetch will almost certainly be cached.
In addition, if the compiler can prove that it knows the correct destructor, it will elide the polymorphic lookup entirely (with optimisations turned on, of course).
To cut a long story short, if this is your only performance concern then you have no performance concerns. If you have performance concerns and you think it's because of virtual destructors, then with respect, you are certainly mistaken.
Example code:
#include <iostream>
#include <memory>
struct A {
~A() { std::cout << "~A\n"; }
};
struct B : A {
~B() { std::cout << "~B\n"; }
};
struct poly_deleter {
template<class T>
struct tag {
};
template<class T>
static void delete_it(void *p) { delete reinterpret_cast<T *>(p); }
template<class T>
poly_deleter(tag<T>) : deleter_(&delete_it<T>) {}
void operator()(void *p) const { deleter_(p); }
void (*deleter_)(void *) = nullptr;
};
template<class T> using unique_poly_ptr = std::unique_ptr<T, poly_deleter>;
template<class T, class...Args> auto make_unique_poly(Args&&...args) -> unique_poly_ptr<T>
{
return unique_poly_ptr<T> {
new T (std::forward<Args>(args)...),
poly_deleter(poly_deleter::tag<T>())
};
};
int main()
{
auto pb = make_unique_poly<B>();
auto pa = std::move(pb);
pa.reset();
}
expected output:
~B
~A