Forward declaration with reference-counting smart pointer - c++

I have implemented a class reference<T> that keeps track of the amount of references to a T which derives from reference_countable.
I have a problem with it with respect to forward declaring the T of the reference<T>, similar to that of std::unique_ptr<T>. With std::unique_ptr<T> the issue comes from the destructor not beeing known, so all you have to do is put the destructor of your class into the cpp file, like so:
header:
class MyClass;
class A
{
std::unique_ptr<MyClass> my_class;
}
implementation:
A:~A() = default;
However, in my version, where std::unique_ptr<MyClass> is replaced by reference<MyClass>, the reference_countable must also be decremented and incremented. Which requires me to also put the copy-assignment and copy-constructor of A in cpp file when MyClass is only forward declared.
Is there a way to avoid having to put the implementation for these three functions in the cpp file for all classes with a reference<T> member?
I've tried to describe it as simple as I can, but for more detail, here is a simple version of the problem. Specifically, the need to define the copy-constructor of A in a.cpp.
my_class.h
#pragma once
#include "minimal_ref_counter.h"
class MyClass : public minimal_reference_countable
{
};
a.h
#pragma once
#include "minimal_ref_counter.h"
class MyClass;
class A
{
public:
A();
~A();
A(const A&);
minimal_reference_counter<MyClass> my_class;
};
a.cpp
#include "test.h"
#include "myclass.h"
A::A()
: my_class(new MyClass())
{}
A::~A() = default;
A::A(const A&) = default;
some_other_code.cpp
#include "a.h"
void some_function()
{
A a1;
A a2 = a1; // this code does not compile without the copy assignment operator beeing implemented externally.
}
minimal_ref_counter.h
#pragma once
#include <atomic>
class minimal_reference_countable
{
template<typename T>
friend class minimal_reference_counter;
std::atomic_int m_references = 0;
auto reference_count() const { return m_references.load(); }
void decrement() { --m_references; }
void increment() { ++m_references; }
};
template<typename T>
class minimal_reference_counter
{
public:
minimal_reference_counter(T* t = nullptr)
{
assign(t);
}
~minimal_reference_counter()
{
reset();
}
minimal_reference_counter(const minimal_reference_counter& r)
{
*this = r;
}
minimal_reference_counter(minimal_reference_counter&& r)
{
*this = std::move(r);
}
minimal_reference_counter& operator=(const minimal_reference_counter& r)
{
assign(r.m_ptr);
return *this;
}
minimal_reference_counter& operator=(minimal_reference_counter&& r)
{
assign(r.m_ptr);
r.reset();
return *this;
}
void reset()
{
if (!m_ptr) return;
m_ptr->decrement();
if (m_ptr->reference_count() == 0)
{
delete m_ptr;
}
m_ptr = nullptr;
}
private:
void assign(T* ptr)
{
reset();
m_ptr = ptr;
if (m_ptr) m_ptr->increment();
}
T* m_ptr = nullptr;
};

C++ templates are lazy. Instantiations are deferred until necessary. The reason to put the destructor in the cpp file when you have a member std::unique_ptr of an incomplete type, later completed, is that the destructor needs the destructor of unique_ptr, thus instantiating it, which needs the destructor of the pointed-to type which requires that type to be complete. This chain of dependencies triggered by the definition of the destructor must be deferred until such time as the pointed-to type is complete.
In your reference counted pointer case, you wonder if the copy constructor, copy assignment operator, and destructor of the pointer necessarily require completeness of the pointed-to type, and likewise their instantiations be deferred until such time as the pointed to type is complete.
Because the pointer destructor potentially invokes the destructor of the pointed-to type, that type necessarily must be complete when the pointer destructor is instantiated. This is similar to unique_ptr and so should be unsurprising.
Similarly, the pointer copy assignment potentially invokes the destructor of the pointed-to type. It replaces the pointee with another pointee and decrements the original pointee's reference count and destroys it if necessary. So, for the same reason as the destructor, the pointer copy assignment operator requires the pointed-to type necessarily be complete when it is instantiated.
The copy constructor is more subtle. No potential destructions of the pointed-to type are invoked. However, with this implementation, we act on a base class of the pointed-to type to increment the reference count. This requires completeness, as otherwise there are no base classes. So, with this implementation, yes, the poitned-to type necessarily must be complete when the copy constructor is instantiated.
There are alternative implementations, of course. One could keep both a T* and a minimal_reference_counter<T>*, instead of finding the latter from the former as a base class. shared_ptr does this. In fact, one could also store the destructor as a function pointer and not require the other awkward instantiation deferrals. shared_ptr does this too.

Related

cannot initialize std::unique_ptr class member [duplicate]

How do I implement a copy constructor for a class that has a unique_ptr member variable? I am only considering C++11.
Since the unique_ptr can not be shared, you need to either deep-copy its content or convert the unique_ptr to a shared_ptr.
class A
{
std::unique_ptr< int > up_;
public:
A( int i ) : up_( new int( i ) ) {}
A( const A& a ) : up_( new int( *a.up_ ) ) {}
};
int main()
{
A a( 42 );
A b = a;
}
You can, as NPE mentioned, use a move-ctor instead of a copy-ctor but that would result in different semantics of your class. A move-ctor would need to make the member as moveable explicitly via std::move:
A( A&& a ) : up_( std::move( a.up_ ) ) {}
Having a complete set of the necessary operators also leads to
A& operator=( const A& a )
{
up_.reset( new int( *a.up_ ) );
return *this,
}
A& operator=( A&& a )
{
up_ = std::move( a.up_ );
return *this,
}
If you want to use your class in a std::vector, you basically have to decide if the vector shall be the unique owner of an object, in which case it would be sufficient to make the class moveable, but not copyable. If you leave out the copy-ctor and copy-assignment, the compiler will guide your way on how to use a std::vector with move-only types.
The usual case for one to have a unique_ptr in a class is to be able to use inheritance (otherwise a plain object would often do as well, see RAII). For this case, there is no appropriate answer in this thread up to now.
So, here is the starting point:
struct Base
{
//some stuff
};
struct Derived : public Base
{
//some stuff
};
struct Foo
{
std::unique_ptr<Base> ptr; //points to Derived or some other derived class
};
... and the goal is, as said, to make Foo copiable.
For this, one needs to do a deep copy of the contained pointer to ensure the derived class is copied correctly.
This can be accomplished by adding the following code:
struct Base
{
//some stuff
auto clone() const { return std::unique_ptr<Base>(clone_impl()); }
protected:
virtual Base* clone_impl() const = 0;
};
struct Derived : public Base
{
//some stuff
protected:
virtual Derived* clone_impl() const override { return new Derived(*this); };
};
struct Foo
{
std::unique_ptr<Base> ptr; //points to Derived or some other derived class
//rule of five
~Foo() = default;
Foo(Foo const& other) : ptr(other.ptr->clone()) {}
Foo(Foo && other) = default;
Foo& operator=(Foo const& other) { ptr = other.ptr->clone(); return *this; }
Foo& operator=(Foo && other) = default;
};
There are basically two things going on here:
The first is the addition of a user-defined copy constructor of Foo, This is necessary, as the unique_ptr-member iself has no copy constructor. In the declared copy-constructor, a new unique_ptr is created, and the pointer is set to a copy of the original pointee.
In case inheritance is involved, the copy of the original pointee must be done carefully. The reason is that doing a simple copy via std::unique_ptr<Base>(*ptr) in the code above would result in slicing, i.e., only the base component of the object gets copied, while the derived part is missing.
To avoid this, the copy has to be done via the clone-pattern. The
idea is to do the copy through a virtual function clone_impl()
which returns a Base* in the base class. In the derived class,
however, it is extended via covariance to return a Derived*, and
this pointer points to a newly created copy of the derived class. The
base class can then access this new object via the base class pointer
Base*, wrap it into a unique_ptr, and return it via the actual
clone() function which is called from the outside.
Second, by declaring a user-defined copy-constructor as done above, the move constructor gets deleted by the corresponding C++ language rules. The declaration via Foo(Foo &&) = default is thus just to let the compiler know that the standard move constructor still applies.
Try this helper to create deep copies, and cope when the source unique_ptr is null.
template< class T >
std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source)
{
return source ? std::make_unique<T>(*source) : nullptr;
}
Eg:
class My
{
My( const My& rhs )
: member( copy_unique(rhs.member) )
{
}
// ... other methods
private:
std::unique_ptr<SomeType> member;
};
Daniel Frey mention about copy solution,I would talk about how to move the unique_ptr
#include <memory>
class A
{
public:
A() : a_(new int(33)) {}
A(A &&data) : a_(std::move(data.a_))
{
}
A& operator=(A &&data)
{
a_ = std::move(data.a_);
return *this;
}
private:
std::unique_ptr<int> a_;
};
They are called move constructor and move assignment
you could use them like this
int main()
{
A a;
A b(std::move(a)); //this will call move constructor, transfer the resource of a to b
A c;
a = std::move(c); //this will call move assignment, transfer the resource of c to a
}
You need to wrap a and c by std::move because they have a name
std::move is telling the compiler to transform the value to
rvalue reference whatever the parameters are
In technical sense, std::move is analogy to something like "std::rvalue"
After moving, the resource of the unique_ptr is transfer to another unique_ptr
There are many topics that document rvalue reference; this is a pretty easy one to begin with.
Edit :
The moved object shall remain valid but unspecified state.
C++ primer 5, ch13 also give a very good explanation about how to "move" the object
I suggest use make_unique
class A
{
std::unique_ptr< int > up_;
public:
A( int i ) : up_(std::make_unique<int>(i)) {}
A( const A& a ) : up_(std::make_unique<int>(*a.up_)) {};
int main()
{
A a( 42 );
A b = a;
}
unique_ptr is not copyable, it is only moveable.
This will directly affect Test, which is, in your second, example also only moveable and not copyable.
In fact, it is good that you use unique_ptr which protects you from a big mistake.
For example, the main issue with your first code is that the pointer is never deleted which is really, really bad. Say, you would fix this by:
class Test
{
int* ptr; // writing this in one line is meh, not sure if even standard C++
Test() : ptr(new int(10)) {}
~Test() {delete ptr;}
};
int main()
{
Test o;
Test t = o;
}
This is also bad. What happens, if you copy Test? There will be two classes that have a pointer that points to the same address.
When one Test is destroyed, it will also destroy the pointer. When your second Test is destroyed, it will try to remove the memory behind the pointer, as well. But it has already been deleted and we will get some bad memory access runtime error (or undefined behavior if we are unlucky).
So, the right way is to either implement copy constructor and copy assignment operator, so that the behavior is clear and we can create a copy.
unique_ptr is way ahead of us here. It has the semantic meaning: "I am unique, so you cannot just copy me." So, it prevents us from the mistake of now implementing the operators at hand.
You can define copy constructor and copy assignment operator for special behavior and your code will work. But you are, rightfully so (!), forced to do that.
The moral of the story: always use unique_ptr in these kind of situations.

Creating unique_ptr<Base> when Base class has a protected destructor

class Base {
public:
Base() {}
virtual void print()const = 0;
protected:
virtual ~Base() { std::cout << "Base destructor\n\n"; }
};
int main()
{
//std::vector<std::unique_ptr<Base>> v1;
//The line above won't compile because: 'Base::~Base': cannot access protected member declared in class 'Base'
std::vector<std::shared_ptr<Base>> v2;
return 0;
}
What is trying to call the destructor when I am creating the vector? Why it won't compile for the unique_ptr vector but would compile for the shared_ptr vector?
Local variables v1 and v2 have automatic storage duration and will be automatically destructed when they go out of scope. The std::vector is irrelevant here: inside vector::~vector() the compiler will generate code for element destructors. Even if a vector is always empty (this is a run-time property!), this code still has to be generated. So let's simplify the code:
std::unique_ptr<Base> v1;
std::shared_ptr<Base> v2;
When v1 goes out of scope, it has to be destroyed. The destructor generated by the compiler boils down to (*):
~unique_ptr() {
delete ptr;
}
To generate code for delete ptr, the compiler needs the accessible destructor. It is protected, so the compilation fails.
Now let's look at v2. The compiler has to generate the destructor, too. But shared_ptr has a type-erased deleter for a managed object. This means that a managed object destructor will be called indirectly – through a virtual function:
struct shared_ptr_deleter_base {
virtual void destroy() = 0;
virtual ~shared_ptr_deleter_base() = default;
};
~shared_ptr() {
// member shared_ptr::deleter has type shared_ptr_deleter_base*
if (deleter)
deleter->destroy();
}
To generate code for deleter->destroy(), you don't need to access Base::~Base() at all. The default constructor of shared_ptr simply sets deleter to a null pointer:
shared_ptr() {
deleter = nullptr;
}
That's why std::shared_ptr<Base> v2; compiles: not only Base::~Base() is not called at the run-time, no call is ever generated by the compiler at the compile-time.
Let's consider this line:
std::shared_ptr<Base> v2(new Base());
Now the following constructor is called (note that it is a template with a separate parameter U that can be different from T in shared_ptr<T>):
template<class U>
shared_ptr(U* ptr) {
deleter = new shared_ptr_deleter<U>(ptr);
}
Here shared_ptr_deleter is a concrete class derived from shared_ptr_deleter_base:
template<class T>
struct shared_ptr_deleter : shared_ptr_deleter_base {
T* ptr;
shared_ptr_deleter(T* p) : ptr(p) {}
virtual void destroy() {
delete ptr;
}
};
To generate code for the constructor taking new Base(), the compiler has to generate code for shared_ptr_deleter<Base>::destroy(). Now it fails because the Base::~Base() is inaccessible.
(*) I present only simplified definitions just to demonstrate basic ideas without going into all the details that are not relevant to understanding the problem in question.
std::unique_ptr can't access the destructor of Base because it's protected. std::shared_ptr uses a polymorphic deleter so std::shared_ptr only needs to access the destructor of Base when you create a new std::shared_ptr.
// this fails because the destructor of Base is inaccessible
std::unique_ptr<Base> a;
// this is ok because the destructor isn't required to instantiate the type
std::shared_ptr<Base> b;
// this fails because make_shared needs the destructor
std::shared_ptr<Base> c = std::make_shared<Base>();
"Polymorphic deleter" basically means that std::shared_ptr stores a pointer to a function that destroys the object. std::unique_ptr uses a "static deleter" that destroys the object directly. Here's some pseudo-code:
struct shared_ptr {
~shared_ptr() {
deleter();
}
void (*deleter)(); // pointer to function that destroys the object
};
// shared_ptr doesn't try to call the destructor directly so we don't need access
// so this is ok
shared_ptr a;
shared_ptr make_shared() {
// here we generate (with templates) a function that calls Base::~Base
// then we set "deleter" to point to that function
// the destructor has to be accessible for us to do this
}
// so we get an error here
shared_ptr b = make_shared();
struct unique_ptr {
~unique_ptr() {
// unique_ptr calls the Base destructor directly
// unique_ptr needs access to the destructor to instantiate the type
}
};
// so we get an error here
unique_ptr c;
In your situation, Base happens to be abstract so you can use std::shared_ptr<Base> because you'll never need to write std::make_shared<Base>(). As long as subclasses of Base have public destructors, std::make_shared will be able to access them without giving you errors.

Why does a noexcept constructor require instantiation of the destructor?

In the following code, a wrapper<T> object is declared which contains a movable<T>, where T is an incomplete type. The destructor of movable is made so that it cannot be instantiated without complete knowledge of T, but wrapper's destructor is only forward-declared, which means that it should be sufficient if ~movable() is instantiated at the point of definition of ~wrapper().
#include <utility>
template<class T>
struct movable {
movable() noexcept = default;
~movable() noexcept { (void) sizeof(T); }
movable(const movable&) noexcept = delete;
movable(movable &&) noexcept = default;
};
template<class T>
class wrapper {
public:
movable<T> m;
wrapper() noexcept = default;
wrapper(wrapper &&) noexcept = default;
~wrapper();
};
struct incomplete;
int main() {
/* extern */ wrapper<incomplete> original;
wrapper<incomplete> copy(std::move(original));
}
(Try it here)
However, wrapper() wants to instantiate ~movable(). I get that in case of an exception, destruction of members must be possible, but movable() and wrapper() are both noexcept. Interestingly, the move constructor works fine (try uncommenting the extern part in the example code.)
What is the reason for this behaviour, and is there a way to circumvent it?
As observed by T.C.,
In a non-delegating constructor, the destructor for [...] each non-static data member of class type is potentially invoked [...]
Per DR1424, the motivation is to make it clear that an implementation is required to issue an error if a destructor is inaccessible from the constructor of the parent object, "[even if] there is no possibility for an exception to be thrown following a given sub-object's construction".
The destructor of movable<T> is accessible, but it cannot be instantiated, which is where your problem arises as a potentially invoked destructor is odr-used.
This makes life simpler for the implementor, as they can just verify that each subobject has an accessible and if necessary instantiable destructor, and leave it to the optimizer to eliminate destructor calls that are not required. The alternative would be horribly complicated - a destructor would be required or not required depending on whether any succeeding subobjects were noexcept constructible, and on the constructor body.
The only way to avoid potential invocation of the destructor would be to use placement new, taking over management of the lifetime of the subobject yourself:
#include <new>
// ...
template<class T>
class wrapper {
public:
std::aligned_storage_t<sizeof(movable<T>), alignof(movable<T>)> m;
wrapper() noexcept { new (&m) movable<T>; };
wrapper(wrapper&& rhs) noexcept { new (&m) movable<T>{reinterpret_cast<movable<T>&&>(rhs.m)}; }
~wrapper();
};

Copy constructor for a class with unique_ptr

How do I implement a copy constructor for a class that has a unique_ptr member variable? I am only considering C++11.
Since the unique_ptr can not be shared, you need to either deep-copy its content or convert the unique_ptr to a shared_ptr.
class A
{
std::unique_ptr< int > up_;
public:
A( int i ) : up_( new int( i ) ) {}
A( const A& a ) : up_( new int( *a.up_ ) ) {}
};
int main()
{
A a( 42 );
A b = a;
}
You can, as NPE mentioned, use a move-ctor instead of a copy-ctor but that would result in different semantics of your class. A move-ctor would need to make the member as moveable explicitly via std::move:
A( A&& a ) : up_( std::move( a.up_ ) ) {}
Having a complete set of the necessary operators also leads to
A& operator=( const A& a )
{
up_.reset( new int( *a.up_ ) );
return *this,
}
A& operator=( A&& a )
{
up_ = std::move( a.up_ );
return *this,
}
If you want to use your class in a std::vector, you basically have to decide if the vector shall be the unique owner of an object, in which case it would be sufficient to make the class moveable, but not copyable. If you leave out the copy-ctor and copy-assignment, the compiler will guide your way on how to use a std::vector with move-only types.
The usual case for one to have a unique_ptr in a class is to be able to use inheritance (otherwise a plain object would often do as well, see RAII). For this case, there is no appropriate answer in this thread up to now.
So, here is the starting point:
struct Base
{
//some stuff
};
struct Derived : public Base
{
//some stuff
};
struct Foo
{
std::unique_ptr<Base> ptr; //points to Derived or some other derived class
};
... and the goal is, as said, to make Foo copiable.
For this, one needs to do a deep copy of the contained pointer to ensure the derived class is copied correctly.
This can be accomplished by adding the following code:
struct Base
{
//some stuff
auto clone() const { return std::unique_ptr<Base>(clone_impl()); }
protected:
virtual Base* clone_impl() const = 0;
};
struct Derived : public Base
{
//some stuff
protected:
virtual Derived* clone_impl() const override { return new Derived(*this); };
};
struct Foo
{
std::unique_ptr<Base> ptr; //points to Derived or some other derived class
//rule of five
~Foo() = default;
Foo(Foo const& other) : ptr(other.ptr->clone()) {}
Foo(Foo && other) = default;
Foo& operator=(Foo const& other) { ptr = other.ptr->clone(); return *this; }
Foo& operator=(Foo && other) = default;
};
There are basically two things going on here:
The first is the addition of a user-defined copy constructor of Foo, This is necessary, as the unique_ptr-member iself has no copy constructor. In the declared copy-constructor, a new unique_ptr is created, and the pointer is set to a copy of the original pointee.
In case inheritance is involved, the copy of the original pointee must be done carefully. The reason is that doing a simple copy via std::unique_ptr<Base>(*ptr) in the code above would result in slicing, i.e., only the base component of the object gets copied, while the derived part is missing.
To avoid this, the copy has to be done via the clone-pattern. The
idea is to do the copy through a virtual function clone_impl()
which returns a Base* in the base class. In the derived class,
however, it is extended via covariance to return a Derived*, and
this pointer points to a newly created copy of the derived class. The
base class can then access this new object via the base class pointer
Base*, wrap it into a unique_ptr, and return it via the actual
clone() function which is called from the outside.
Second, by declaring a user-defined copy-constructor as done above, the move constructor gets deleted by the corresponding C++ language rules. The declaration via Foo(Foo &&) = default is thus just to let the compiler know that the standard move constructor still applies.
Try this helper to create deep copies, and cope when the source unique_ptr is null.
template< class T >
std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source)
{
return source ? std::make_unique<T>(*source) : nullptr;
}
Eg:
class My
{
My( const My& rhs )
: member( copy_unique(rhs.member) )
{
}
// ... other methods
private:
std::unique_ptr<SomeType> member;
};
Daniel Frey mention about copy solution,I would talk about how to move the unique_ptr
#include <memory>
class A
{
public:
A() : a_(new int(33)) {}
A(A &&data) : a_(std::move(data.a_))
{
}
A& operator=(A &&data)
{
a_ = std::move(data.a_);
return *this;
}
private:
std::unique_ptr<int> a_;
};
They are called move constructor and move assignment
you could use them like this
int main()
{
A a;
A b(std::move(a)); //this will call move constructor, transfer the resource of a to b
A c;
a = std::move(c); //this will call move assignment, transfer the resource of c to a
}
You need to wrap a and c by std::move because they have a name
std::move is telling the compiler to transform the value to
rvalue reference whatever the parameters are
In technical sense, std::move is analogy to something like "std::rvalue"
After moving, the resource of the unique_ptr is transfer to another unique_ptr
There are many topics that document rvalue reference; this is a pretty easy one to begin with.
Edit :
The moved object shall remain valid but unspecified state.
C++ primer 5, ch13 also give a very good explanation about how to "move" the object
I suggest use make_unique
class A
{
std::unique_ptr< int > up_;
public:
A( int i ) : up_(std::make_unique<int>(i)) {}
A( const A& a ) : up_(std::make_unique<int>(*a.up_)) {};
int main()
{
A a( 42 );
A b = a;
}
unique_ptr is not copyable, it is only moveable.
This will directly affect Test, which is, in your second, example also only moveable and not copyable.
In fact, it is good that you use unique_ptr which protects you from a big mistake.
For example, the main issue with your first code is that the pointer is never deleted which is really, really bad. Say, you would fix this by:
class Test
{
int* ptr; // writing this in one line is meh, not sure if even standard C++
Test() : ptr(new int(10)) {}
~Test() {delete ptr;}
};
int main()
{
Test o;
Test t = o;
}
This is also bad. What happens, if you copy Test? There will be two classes that have a pointer that points to the same address.
When one Test is destroyed, it will also destroy the pointer. When your second Test is destroyed, it will try to remove the memory behind the pointer, as well. But it has already been deleted and we will get some bad memory access runtime error (or undefined behavior if we are unlucky).
So, the right way is to either implement copy constructor and copy assignment operator, so that the behavior is clear and we can create a copy.
unique_ptr is way ahead of us here. It has the semantic meaning: "I am unique, so you cannot just copy me." So, it prevents us from the mistake of now implementing the operators at hand.
You can define copy constructor and copy assignment operator for special behavior and your code will work. But you are, rightfully so (!), forced to do that.
The moral of the story: always use unique_ptr in these kind of situations.

why shared_ptr can access with ingoring the "protected access right"

I made some testing with shared_ptr,and i can't think out the matter below.I just started to learn the boost library. Is there anybody can tell me the reason?
#include <boost\shared_ptr.hpp>
#include <iostream>
class A
{
public:
virtual void sing()
{
std::cout<<"A";
}
protected: virtual ~A() {};
};
class B : public A
{
public:
virtual void sing()
{
std::cout << "B";
}
virtual ~B() {};
};
int foo()
{
boost::shared_ptr<A> pa(new B());
pa->sing();
delete static_cast<B*>(pa.get());
delete pa.get(); //this line has a problem error C2248: “A::~A”: can't access protected memmber(declared in class“A")
return 0;
}
int main()
{
foo();
return 0;
}
but it can be compiled when that line is commented out. Surely it doesn't mean that the shared_ptr will delete the pointer internally maintained out of the main function, just like what i did. Is there any difference between the pointer returned by pa.get() and the pointer internally maintained?
I believe that delete is called during destruction of the shared_ptr on the type of the pointer passed into the constructor. Have a look at the constructor here:
http://www.boost.org/doc/libs/1_49_0/libs/smart_ptr/shared_ptr.htm#constructors
So when your pa goes out of scope, B::~B( ) is called rather than the destructor of the type contained - A::~A ( which would be impossible because it's declared protected).
Actually, it's a bit more complicated than that: the machinery behind shared_ptr is quite complicated.
First, let us prove there is no specific access rights granted to shared_ptr:
int main() {
A* a = new B();
std::shared_ptr<A> p(a); // expected-error
}
This will result in an error because the destructor of A is not accessible. Interestingly, the error occurs at the point of construction, which is a clue...
So, what is the magic behind shared_ptr ?
Internally, a shared_ptr keeps much more than a simple pointer and reference counts. A shared_ptr is built with a deleter, in charge of destructing the instance of the object. Where the design really shines is that this deleter is instantiated in the constructor and thus may know more type information than the bare shared_ptr type lets on.
A simplified demo:
template <typename T>
struct shared_holder {
typedef void (*Disposer)(T*);
explicit shared_holder_base(T* t, Disposer d): _ptr(t), _disposer(d) {}
void dispose() { _disposer(_ptr); _ptr = 0; }
T* _ptr;
Disposer _disposer;
};
template <typename U, typename T>
void dispose(T* t) { delete static_cast<U*>(t); }
template <typename T>
class shared_ptr {
typedef shared_holder<T> holder;
public:
shared_ptr(): _holder(0), _ptr(0) {}
template <typename U>
explicit shared_ptr(U* u):
_holder(new holder(u, dispose<U, T>)), _ptr(_holder->_ptr) {}
private:
holder* _holder;
T* _ptr;
};
The key insight is that the disposer is instantiated from the static type known by the constructor; this is why:
shared_ptr<A>(new B) works
A* a = new B; shared_ptr<A>(a) does not
You can read the Boost headers, the machinery behind the shared_ptr is quite interesting.
As an exercise for the reader, why does shared_ptr<T> has a _ptr member ?
When you have:
boost::shared_ptr<A> pa(new B());
...you are calling boost::shared_ptr constructor and are dealing with TWO template parameters:
shared_ptr template type T (A in your case);
get() returns T* so when you tried:
delete pa.get();
...you tried to access ~A() which is accessible only to As children and therefore got an error.
In the following line:
delete static_cast<B*>(pa.get());
...you were downcasting A* to B* and called delete on B* thus invoking ~B() to which you had access. (~B() is always calling ~A() for ~A() is declared as virtual)
shared_ptr constructor argument template type Y (B in your case) with the
requirement that Y* must be convertible to T* (you can upcast
B* to A* as B inherits A in your case).
~shared_ptr() calls delete on Y* (B* in your case; ~B() can access ~A() and
calls it) and this is what happens when pa goes out of scope (and this is how shared_ptr accessed base class protected destructor which was your original question).
boost::shared_ptr keeps internally a single pointer. When creating boost::shared_ptr you are passing to its constructor a SINGLE pointer which can be regarded as / converted to pointer to any of those TWO template argument types.
I believe it is because the definition of the boost template declares the sharedpointer a friend of the <Class T> class from your template instantiation.
This is a snipet I copied from the boot shared pointer header file.
// Tasteless as this may seem, making all members public allows member templates
// to work in the absence of member template friends. (Matthew Langston)
#ifndef BOOST_NO_MEMBER_TEMPLATE_FRIENDS
private:
template<class Y> friend class shared_ptr;
template<class Y> friend class weak_ptr;
#endif