I've read an "Item" about shared_ptr in Scott Meyers' book "Effective Modern C++" where he says the following:
The usual control block implementation is more
sophisticated than you might expect. It makes use of inheritance, and there’s even a virtual function. (It’s used to ensure that the pointed-to object is properly destroyed.)
That means that using std::shared_ptrs also incurs the cost of the machinery for the virtual function used by the control block.
Then he doesn't explain what a virtual function does exactly. As far as I know, the proper way of deleting a pointed object is using deleters or type erasure. So, please explain what this is about.
The virtual function is required to ensure the object being shared is appropriately deleted. Unlike a unique_ptr, shared_ptr does not require full knowledge of the type when its template is instantiated. E.g. you can do this:
class Foo;
std::shared_ptr<Foo> foo = make_foo();
Note that in the code above we don't have the complete Foo type, just the forward declaration. If we let foo go out of scope, the object it is pointing to will be correctly deleted because when Foo was created in make_foo, a deleter was also created which knows the complete type of Foo, and hence can call the appropriate destructors. (E.g. Perhaps make_foo creates a Bar that inherits from Foo and returns that. shared_ptr's will handle this fine.)
The function on the deleter object that shared_ptr creates to manage the deletion of Foo will be virtual, allowing shared_ptr to call the correct destructor.
Roughly this could be something like this:
struct deleter_interface {
virtual void ~deleter_interface = default;
virtual void dispose() = 0;
};
template <typename T>
struct deleter : deleter_interface {
T* ptr_;
deleter(T* ptr) : ptr_(ptr) {}
virtual void dispose() { delete ptr_; }
};
template <typename T>
shared_ptr {
T* ptr_;
deleter_interface* deleter_;
...
};
template <typename T>
shared_ptr<T>::shared_ptr<T>(T* ptr)
: ptr_(ptr), deleter_(new deleter<T>(ptr)) {}
template <typename T>
shared_ptr<T>::~shared_ptr<T>() { deleter_->dispose(); delete deleter_; }
While this seems more complicated that is strictly necessary, it is to allow shared_ptr to be used without the complete type. For example what if you wanted to do this:
In a.h:
struct Foo;
std::shared_ptr<Foo> a = make_object();
// ... let a go out of scope
And in a.cc:
struct Foo { ... };
struct Bar : Foo { ... };
std::shared_ptr<Foo> make_object() { return std::shared_ptr<Foo>(new Bar); }
If we didn't have the virtual function used in the deleter code, then Bar would not be destructed correctly. With the virtual function it doesn't matter that the header (a.h) never sees the definition of Foo or Bar.
Related
I was reading the Smart Pointer Programming Techniques provided in the boost documentation.
In the Section "using abstract classes for implementation hiding", they provide a nice idiom to fully hide an implementation behind pure virtual interface. For example:
// Foo.hpp
#include <memory>
class Foo {
public:
virtual void Execute() const = 0;
protected:
~Foo() = default;
};
std::shared_ptr<const Foo> MakeFoo();
and
// Foo.cpp
#include "Foo.hpp"
#include <iostream>
class FooImp final
: public Foo {
public:
FooImp() = default;
FooImp(const FooImp&) = delete;
FooImp& operator=(const FooImp&) = delete;
void Execute() const override {
std::cout << "Foo::Execute()" << std::endl;
}
};
std::shared_ptr<const Foo> MakeFoo() {
return std::make_shared<const FooImp>();
}
Regarding the protected, non-virtual destructor in class Foo, the document states:
Note the protected and nonvirtual destructor in the example above. The
client code cannot, and does not need to, delete a pointer to X; the
shared_ptr<X> instance returned from createX will correctly call
~X_impl.
which I believe I understand.
Now, it seems to me that this nice idiom could be used to produce a singleton-like entity if a factory function returned std::unique_ptr<Foo>; the user would be forced to move the pointer, with a compile-time guarantee that no copies exist.
But, alas, I cannot make the code work unless I change ~Foo() = default from protected to public, and I do not understand why.
In other words, this does not work:
std::unique_ptr<const Foo> MakeUniqueFoo() {
return std::make_unique<const FooImp>();
}
My questions:
Could you explain me why do I need to make public ~Foo() = default?
Would it be dangerous to just remove protected?
Is the singleton-like idea even worth it?
The issue has to do with how deleters work in smart pointers.
In shared_ptr, the deleter is dynamic. When you have std::make_shared<const FooImp>();, the deleter in that object will call ~FooImpl() directly:
The object is destroyed using delete-expression or a custom deleter that is supplied to shared_ptr during construction.
That deleter will be copied onto the shared_ptr<const Foo> when it's created.
In unique_ptr, the deleter is part of the type. It's:
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
So when you have unique_ptr<const Foo>, that will call ~Foo() directly - which is impossible since ~Foo() is protected. That's why when you make Foo() public, it works. Works, as in, compiles. You would have to make it virtual too - otherwise you'd have undefined behavior by only destructing the Foo part of FooImpl.
It's not dangerous. Unless you forget to make the destructor virtual, which, it bears repeating, will cause undefined behavior.
This isn't really singleton-like. As to whether or not it's worth it? Primarily opinion based.
Every shared_ptr stores 4 things: a pointer, a strong reference count, a weak reference count, and a deleter.
The deleter gets the type from which the shared_ptr was constructed from, and deletes that type, not the exposed type. If you cast it to a base shared_ptr, the derived deleter is still stored.
unique_ptr does not, by default, store such a stateful deleter.
The design reason behind this is that a shared_ptr is already managing extra resources: adding that deleter is cheap, given that you are already managing the reference counts.
For unique_ptr, without a stateful deleter, the overhead for it is basically identical to a raw pointer. Adding a stateful deleter by default would make unique_ptrs significantly more expensive.
While they are both smart pointers, unique_ptr is really minimal, while shared_ptr is far more complex.
You can get around this by appending a stateful deleter to unique_ptr.
struct stateful_delete {
void const* ptr = nullptr;
void(*f)(void const*) = nullptr;
template<class T>
stateful_delete(T const* t):
ptr(t),
f([](void const* ptr){
delete static_cast<T const*>(ptr);
})
{}
template<class T>
void operator()(T*)const{
if (f) f(ptr);
}
};
template<class T>
using unique_ptr_2 = std::unique_ptr<T, stateful_delete>;
template<class T>
unique_ptr_2<T> unique_wrap_2(T* t) {
return {t, t};
}
template<class T, class...Args>
unique_ptr_2<T> make_unique_2(Args&&...args) {
return unique_wrap( new T(std::forward<Args>(args)...) );
}
such unique_ptr_2 are 3 times as large as a unique_ptr. They do not do an extra allocation (unlike shared_ptr). And they will work with your non-virtual protected ~Foo with a public ~FooImpl.
You could reduce the size of unique_ptr_2 down to 2 pointers if we use the make_shared technique of doing a unified allocation, and store the equivalent of the ptr and f on the heap. I'm uncertain if that complexity is worth the savings.
Based on Barry's answer an alternative to making it public is defining your own deleter which has access to your class' ~Foo() method.
Example (tried with VS2013):
template <typename T>
class deleter
{
public:
void operator()(T* a)
{
// Explicitly call the destructor on a.
a->~A();
}
};
class A {
friend class deleter<A>; // Grant access to the deleter.
protected:
~A() {
// Destructor.
}
};
std::unique_ptr<A, deleter<A>> MakeA()
{
return std::unique_ptr<A, deleter<A>>(new A());
}
I'm using the pimpl-idiom with std::unique_ptr:
class window {
window(const rectangle& rect);
private:
class window_impl; // defined elsewhere
std::unique_ptr<window_impl> impl_; // won't compile
};
However, I get a compile error regarding the use of an incomplete type, on line 304 in <memory>:
Invalid application of 'sizeof' to an incomplete type 'uixx::window::window_impl'
For as far as I know, std::unique_ptr should be able to be used with an incomplete type. Is this a bug in libc++ or am I doing something wrong here?
Here are some examples of std::unique_ptr with incomplete types. The problem lies in destruction.
If you use pimpl with unique_ptr, you need to declare a destructor:
class foo
{
class impl;
std::unique_ptr<impl> impl_;
public:
foo(); // You may need a def. constructor to be defined elsewhere
~foo(); // Implement (with {}, or with = default;) where impl is complete
};
because otherwise the compiler generates a default one, and it needs a complete declaration of foo::impl for this.
If you have template constructors, then you're screwed, even if you don't construct the impl_ member:
template <typename T>
foo::foo(T bar)
{
// Here the compiler needs to know how to
// destroy impl_ in case an exception is
// thrown !
}
At namespace scope, using unique_ptr will not work either:
class impl;
std::unique_ptr<impl> impl_;
since the compiler must know here how to destroy this static duration object. A workaround is:
class impl;
struct ptr_impl : std::unique_ptr<impl>
{
~ptr_impl(); // Implement (empty body) elsewhere
} impl_;
As Alexandre C. mentioned, the problem comes down to window's destructor being implicitly defined in places where the type of window_impl is still incomplete. In addition to his solutions, another workaround that I've used is to declare a Deleter functor in the header:
// Foo.h
class FooImpl;
struct FooImplDeleter
{
void operator()(FooImpl *p);
};
class Foo
{
...
private:
std::unique_ptr<FooImpl, FooImplDeleter> impl_;
};
// Foo.cpp
...
void FooImplDeleter::operator()(FooImpl *p)
{
delete p;
}
Note that using a custom Deleter function precludes the use of std::make_unique (available from C++14), as already discussed here.
use a custom deleter
The problem is that unique_ptr<T> must call the destructor T::~T() in its own destructor, its move assignment operator, and unique_ptr::reset() member function (only). However, these must be called (implicitly or explicitly) in several PIMPL situations (already in the outer class's destructor and move assignment operator).
As already pointed out in another answer, one way to avoid that is to move all operations that require unique_ptr::~unique_ptr(), unique_ptr::operator=(unique_ptr&&), and unique_ptr::reset() into the source file where the pimpl helper class is actually defined.
However, this is rather inconvenient and defies the very point of the pimpl idoim to some degree. A much cleaner solution that avoids all that is to use a custom deleter and only move its definition into the source file where the pimple helper class lives. Here is a simple example:
// file.h
class foo
{
struct pimpl;
struct pimpl_deleter { void operator()(pimpl*) const; };
std::unique_ptr<pimpl,pimpl_deleter> m_pimpl;
public:
foo(some data);
foo(foo&&) = default; // no need to define this in file.cc
foo&operator=(foo&&) = default; // no need to define this in file.cc
//foo::~foo() auto-generated: no need to define this in file.cc
};
// file.cc
struct foo::pimpl
{
// lots of complicated code
};
void foo::pimpl_deleter::operator()(foo::pimpl*ptr) const { delete ptr; }
Instead of a separate deleter class, you can also use a free function or static member of foo:
class foo {
struct pimpl;
static void delete_pimpl(pimpl*);
using deleter = void(&)(pimpl*);
std::unique_ptr<pimpl,deleter> m_pimpl;
public:
foo(some data);
};
Probably you have some function bodies within .h file within class that uses incomplete type.
Make sure that within your .h for class window you have only function declaration. All function bodies for window must be in .cpp file. And for window_impl as well...
Btw, you have to explicitly add destructor declaration for windows class in your .h file.
But you CANNOT put empty dtor body in you header file:
class window {
virtual ~window() {};
}
Must be just a declaration:
class window {
virtual ~window();
}
To add to the other's replies about the custom deleter, in our internal "utilities library" I added a helper header to implement this common pattern (std::unique_ptr of an incomplete type, known only to some of the TU to e.g. avoid long compile times or to provide just an opaque handle to clients).
It provides the common scaffolding for this pattern: a custom deleter class that invokes an externally-defined deleter function, a type alias for a unique_ptr with this deleter class, and a macro to declare the deleter function in a TU that has a complete definition of the type. I think that this has some general usefulness, so here it is:
#ifndef CZU_UNIQUE_OPAQUE_HPP
#define CZU_UNIQUE_OPAQUE_HPP
#include <memory>
/**
Helper to define a `std::unique_ptr` that works just with a forward
declaration
The "regular" `std::unique_ptr<T>` requires the full definition of `T` to be
available, as it has to emit calls to `delete` in every TU that may use it.
A workaround to this problem is to have a `std::unique_ptr` with a custom
deleter, which is defined in a TU that knows the full definition of `T`.
This header standardizes and generalizes this trick. The usage is quite
simple:
- everywhere you would have used `std::unique_ptr<T>`, use
`czu::unique_opaque<T>`; it will work just fine with `T` being a forward
declaration;
- in a TU that knows the full definition of `T`, at top level invoke the
macro `CZU_DEFINE_OPAQUE_DELETER`; it will define the custom deleter used
by `czu::unique_opaque<T>`
*/
namespace czu {
template<typename T>
struct opaque_deleter {
void operator()(T *it) {
void opaque_deleter_hook(T *);
opaque_deleter_hook(it);
}
};
template<typename T>
using unique_opaque = std::unique_ptr<T, opaque_deleter<T>>;
}
/// Call at top level in a C++ file to enable type %T to be used in an %unique_opaque<T>
#define CZU_DEFINE_OPAQUE_DELETER(T) namespace czu { void opaque_deleter_hook(T *it) { delete it; } }
#endif
May be not a best solution, but sometimes you may use shared_ptr instead.
If course it's a bit an overkill, but... as for unique_ptr, I'll perhaps wait 10 years more until C++ standard makers will decide to use lambda as a deleter.
Another side.
Per your code it may happen, that on destruction stage window_impl will be incomplete. This could be a reason of undefined behaviour.
See this:
Why, really, deleting an incomplete type is undefined behaviour?
So, if possible I would define a very base object to all your objects, with virtual destructor. And you're almost good. You just should keep in mind that system will call virtual destructor for your pointer, so you should define it for every ancestor. You should also define base class in inheritance section as a virtual (see this for details).
Using extern template
The issue with using std::unique_ptr<T> where T is an incomplete type is that unique_ptr needs to be able to delete an instance of T for various operations. The class unique_ptr uses std::default_delete<T> to delete the instance. Hence, in an ideal world, we would just write
extern template class std::default_delete<T>;
to prevent std::default_delete<T> from being instantiated. Then, declaring
template class std::default_delete<T>;
at a place where T is complete, would instantiate the template.
The issue here is that default_delete actually defines inline methods that will not be instantiated. So, this idea does not work. We can, however, work around this problem.
First, let us define a deleter that does not inline the call operator.
/* --- opaque_ptr.hpp ------------------------------------------------------- */
#ifndef OPAQUE_PTR_HPP_
#define OPAQUE_PTR_HPP_
#include <memory>
template <typename T>
class opaque_delete {
public:
void operator() (T* ptr);
};
// Do not move this method into opaque_delete, or it will be inlined!
template <typename T>
void opaque_delete<T>::operator() (T* ptr) {
std::default_delete<T>()(ptr);
}
Furthermore, for ease of use, define a type opaque_ptr which combines unique_ptr with opaque_delete, and analogously to std::make_unique, we define make_opaque.
/* --- opaque_ptr.hpp cont. ------------------------------------------------- */
template <typename T>
using opaque_ptr = std::unique_ptr<T, opaque_delete<T>>;
template<typename T, typename... Args>
inline opaque_ptr<T> make_opaque(Args&&... args)
{
return opaque_ptr<T>(new T(std::forward<Args>(args)...));
}
#endif
The type opaque_delete can now be used with the extern template construction. Here is an example.
/* --- foo.hpp -------------------------------------------------------------- */
#ifndef FOO_HPP_
#define FOO_HPP_
#include "opaque_ptr.hpp"
class Foo {
public:
Foo(int n);
void print();
private:
struct Impl;
opaque_ptr<Impl> m_ptr;
};
// Do not instantiate opaque_delete.
extern template class opaque_delete<Foo::Impl>;
#endif
Since we prevent opaque_delete from being instantiated this code compiles without errors. To make the linker happy, we instantiate opaque_delete in our foo.cpp.
/* --- foo.cpp -------------------------------------------------------------- */
#include "foo.hpp"
#include <iostream>
struct Foo::Impl {
int n;
};
// Force instantiation of opaque_delete.
template class opaque_delete<Foo::Impl>;
The remaining methods could be implemented as follows.
/* --- foo.cpp cont. -------------------------------------------------------- */
Foo::Foo(int n)
: m_ptr(new Impl)
{
m_ptr->n = n;
}
void Foo::print() {
std::cout << "n = " << m_ptr->n << std::endl;
}
The advantage of this solution is that, once opaque_delete is defined, the required boilerplate code is rather small.
I was reading the latest Overload (link) and decided to test out the statement at page 8:
shared_ptr will properly invoke B’s destructor on scope exit, even
though the destructor of A is not virtual.
I am using Visual Studio 2013, compiler v120:
#include <memory>
#include <iostream>
struct A {
~A() { std::cout << "Deleting A"; }
};
struct B : public A
{
~B() { std::cout << "Deleting B"; }
};
int main()
{
std::shared_ptr<A> ptr = std::make_shared<B>();
ptr.reset();
return 0;
}
This works as expected and prints out "Deleting BDeleting A"
The article seems to imply that this should also work with std::unique_ptr:
There is almost no need to manage your own resources so resist the
temptation to implement your own copy/assign/move construct/move
assign/destructor functions.
Managed resources can be resources inside
your class definition or instances of your classes themselves.
Refactoring the code around standard containers and class templates
like unique_ptr or shared_ptr will make your code more readable and
maintainable.
However, when changing
std::shared_ptr<A> ptr = std::make_shared<B>();
to
std::unique_ptr<A> ptr = std::make_unique<B>();
the program will only output "Deleting A"
Did I misunderstand the article and the behavior is intended by the standard?
Is it a bug with the MSVC compiler?
shared_ptr and unique_ptr are different. make_shared will create a type erased deleter object on invocation while with unique_ptr the deleter is part of the type. Therefore the shared_ptr knows the real type when it invokes the deleter but the unique_ptr doesn't. This makes unique_ptr's much more efficient which is why it was implemented this way.
I find the article a bit misleading actually. I do not consider it to be good advice to expose copy constructors of a base class with virtual functions, sounds like a lot of slicing problems to me.
Consider the following situation:
struct A{
virtual void foo(){ std::cout << "base"; };
};
struct B : A{
virtual void foo(){ std::cout << "derived"; };
};
void bar(A& a){
a.foo(); //derived
auto localA = a; //poor matanance porgrammer didn't notice that a is polymorphic
localA.foo(); //base
}
I would personally advocate non-intrusive polymorphism http://isocpp.org/blog/2012/12/value-semantics-and-concepts-based-polymorphism-sean-parent for any new higherarchies, it sidesteps the problem entirely.
This behaviour is according to the standard.
The unique_ptr is designed to have no performance overhead compared to the ordinary new/delete.
The shared_ptr has the allowance to have the overhead, so it can be smarter.
According to the standard [20.7.1.2.2, 20.7.2.2.2], the unique_ptr calls delete on the pointer returned by get(), while the shared_ptr deletes the real object it holds - it remembers the proper type to delete (if properly initialized), even without a virtual destructor.
Obviously, the shared_ptr is not all-knowing, you can trick it to behave badly by passing a pointer to the base object like this:
std::shared_ptr<Base> c = std::shared_ptr<Base> { (Base*) new Child() };
But, that would be a silly thing to do anyhow.
You could do something similar with unique_ptr, but since its deleter type is determined statically, you need to statically maintain the proper deleter type. i.e. (Live demo at Coliru):
// Convert given pointer type to T and delete.
template <typename T>
struct deleter {
template <typename U>
void operator () (U* ptr) const {
if (ptr) {
delete static_cast<T*>(ptr);
}
}
};
// Create a unique_ptr with statically encoded deleter type.
template <typename T, typename...Args>
inline std::unique_ptr<T, deleter<T>>
make_unique_with_deleter(Args&&...args) {
return std::unique_ptr<T, deleter<T>>{
new T(std::forward<Args>(args)...)
};
}
// Convert a unique_ptr with statically encoded deleter to
// a pointer to different type while maintaining the
// statically encoded deleter.
template <typename T, typename U, typename W>
inline std::unique_ptr<T, deleter<W>>
unique_with_deleter_cast(std::unique_ptr<U, deleter<W>> ptr) {
T* t_ptr{ptr.release()};
return std::unique_ptr<T, deleter<W>>{t_ptr};
}
// Create a unique_ptr to T with statically encoded
// deleter for type U.
template <typename T, typename U, typename...Args>
inline std::unique_ptr<T, deleter<U>>
make_unique_with_deleter(Args&&...args) {
return unique_with_deleter_cast<T>(
make_unique_with_deleter<U>(std::forward<Args>(args)...)
);
}
It's a bit awkward to use:
std::unique_ptr<A, deleter<B>> foo = make_unique_with_deleter<A, B>();
auto improves the situation:
auto bar = make_unique_with_deleter<A, B>();
It doesn't really buy you much, since the dynamic type is encoded right there in the static type of the unique_ptr. If you're going to carry the dynamic type around, why not simply use unique_ptr<dynamic_type>? I conjecture such a thing may have some use in generic code, but finding an example of such is left as an exercise to the reader.
The technique std::shared_ptr employs to do the magic is called type erasure. If you are using gcc, try to find the file bits/shared_ptr_base.h and check the implementation. I'm using gcc 4.7.2.
unique_ptr is designed for minimum overhead and does not use type erasure to remember the actual type of the pointer it holds.
Here is a great discussion on this topic: link
EDIT: a simple implementation of shared_ptr to showcase how type erasure is achieved.
#include <cstddef>
// A base class which does not know the type of the pointer tracking
class ref_counter_base
{
public:
ref_counter_base() : counter_(1) {}
virtual ~ref_counter_base() {}
void increase()
{
++counter_;
}
void release()
{
if (--counter_ == 0) {
destroy();
delete this;
}
}
virtual void destroy() = 0;
private:
std::size_t counter_;
};
// The derived class that actually remembers the type of
// the pointer and deletes the pointer on destroy.
template <typename T>
class ref_counter : public ref_counter_base
{
public:
ref_counter(T *p) : p_(p) {}
virtual void destroy()
{
delete p_;
}
private:
T *p_;
};
template <typename T>
class shared_ptr
{
public:
shared_ptr(T *p)
: p_(p)
, counter_(new ref_counter<T>(p))
{
}
// Y* should be implicitely convertable to T*,
// i.e. Y is derived from T
template <typename Y>
shared_ptr(Y &other)
: p_(other.get())
, counter_(other.counter())
{
counter_->increase();
}
~shared_ptr()
{
counter_->release();
}
T* get() { return p_; }
ref_counter_base* counter() { return counter_; }
private:
T *p_;
ref_counter_base *counter_;
};
I am making an class which is managed by a unique_ptr, but for various reasons I need to give implementations access to a raw pointer to the object. However I want to ensure that users don't inadvertently delete the underlying object. I have come up with the following example code:
(It is part of a tree structure, and I need to be able to look at members of tree nodes without actually detaching them. shared_ptr seems like overkill in this situation.)
#include <memory>
using namespace std;
class unOnly
{
~unOnly() {}
public:
unOnly() {}
friend class default_delete<unOnly>;
};
int main()
{
unique_ptr<unOnly> ptr(new unOnly());
}
This compiles for me in gcc 4.4.5. However, can I be sure that in all implementations default_delete is what actually deletes the object, as opposed to some private implementation class? Should I write my own deleter to be sure?
Why not
class unOnly
{
unOnly() {}
~unOnly() {}
struct deleter { void operator()(unOnly* x) { delete x; }};
public:
typedef std::unique_ptr<unOnly, deleter> handle;
static handle create() { return handle(new unOnly); }
};
auto x = unOnly::create();
? Or even
class unOnly
{
~unOnly() {}
struct deleter { void operator()(unOnly* x) { delete x; }};
public:
unOnly() {}
typedef std::unique_ptr<unOnly, deleter> handle;
};
unOnly::handle x(new unOnly);
(I prefer the former, but the latter is perhaps more in the spirit of what you're asking for)
The point of unique_ptr (besides having an object which owns it's pointer) is that you can pass it a custom deleter, so it makes sense just to write one instead of doing something else, which seems unnecessarily complex.
Quoting from the standard:
20.7.1 Class template unique_ptr
6. [...]
template<class T, class D = default_delete<T>> class unique_ptr;
20.7.1.1.1
1 The class template default_delete serves as the default deleter (destruction policy) for the class template unique_ptr.
So, it seems implementations of unique_ptr are required to use default_delete as the default deleter.
EDIT:
But this doesn't mean your approach is foolproof, see #RMartinhoFernandes' comment below.
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