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
; theshared_ptr<X>
instance returned fromcreateX
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:
public
~Foo() = default
?protected
?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.
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