The following code
struct Base
{
public:
Base()
{
std::cout<<"Base Ctr";
}
~Base()
{
std::cout<<"Base Dtr";
}
};
struct Derived : Base
{
Derived()
{
std::cout<<"Derived Ctr";
}
~Base()
{
std::cout<<"Derived Dtr";
}
};
int main()
{
Base* b = new Derived;
delete b;
}
gives me the following output :
Base Ctr
Derived Ctr
Base Dtr
The solution to this is to make the base destructor virtual.
However when I use boost smart pointers without virtual base destructor. I get a different output.
int main()
{
boost::shared_ptr<Base> b = boost::make_shared<Derived>();
}
The output is
Base Ctr
Derived Ctr
Derived Dtr
Base Dtr
How is boost shared_ptr able to achieve this without affecting(I'm assuming) the Base and Derived classes.
How does it scale it for multiple level inheritance, i.e base points to dervderv
where dervderv is inherited from derv.
EDIT:
Most answers tell me that the "magic" happens in make_shared. I however get the same behaviour for the following code
boost::shared_ptr<Base> ptr(new Derived);
In brief, boost::smart_ptr
contains pointer to an object, reference count and a deleter function, which is called in destructor, when you call boost::make_shared<Derived>()
, it will create default-constructed object of class Derived
and deleter will point to a destructor of Derived
. This should work for inheritance chain of any length, but having virtual destructor in the base class is really mandatory.
Consider next simplified naive example implementation of such technique:
#include <iostream>
#include <functional>
using namespace std;
struct Base
{
public:
Base()
{
std::cout<<"Base Ctr ";
}
~Base()
{
std::cout<<"Base Dtr ";
}
};
struct Derived : Base
{
Derived()
{
std::cout<<"Derived Ctr ";
}
Derived(const Derived& d)
{
std::cout<<"Derived copy Ctr ";
}
~Derived()
{
std::cout<<"Derived Dtr ";
}
};
template <typename T>
void default_deleter(T* p) {
delete p;
}
template <typename T>
struct pointer {
pointer(T* p, std::function<void ()> d) : p_(p), deleter_(d) {}
template <typename U>
pointer(pointer<U> other) : p_(new U(*other.p_)),
deleter_(std::bind(&default_deleter<U>, (U*)p_)) {}
template <typename Y>
explicit pointer(Y* p) : p_(p), deleter_(std::bind(&default_deleter<Y>, p)) {}
~pointer() {
deleter_();
}
T* p_;
std::function<void ()> deleter_;
};
template <typename T>
pointer<T> make_pointer() {
T* p = new T;
return pointer<T>(p, std::bind(&default_deleter<T>, p));
}
int main() {
pointer<Base> b = make_pointer<Derived>();
pointer<Base> b2(new Derived);
return 0;
}
The output is:
Base Ctr Derived Ctr Base Ctr Derived copy Ctr Derived Dtr Base Dtr Derived Dtr Base Dtr
destructors of Derived class are called twice because there are 2 instances of pointer : one which was created in make_pointer function and the second is in main function.
A solution implemented using normal function pointers:
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <typeinfo>
using namespace std;
struct Base{
Base(){
cout<<"Base ctor."<<endl;
}
~Base(){
cout<<"Base dtor."<<endl;
}
};
struct Derv: Base{
Derv():Base(){
cout<<"Derv ctor."<<endl;
}
~Derv(){
cout<<"Derv dtor."<<endl;
}
};
typedef void (*DEL)(void*);
template<typename U>
void deleter(void* ptr)
{
delete static_cast<U*>(ptr);
}
template<typename T>
struct SmartPtr{
T* memPtr;
DEL func;
template<typename U>
SmartPtr(U* p):
memPtr(p)
{
func = deleter<U>;
}
~SmartPtr(){
func(memPtr);
}
};
int main()
{
//case 1
SmartPtr<Base> ptrSmart1(new Derv());
//case 2
SmartPtr<Base> ptrSmart2(new Base());
//case 3
SmartPtr<Derv> ptrSmart3(new Derv());
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With