While reading through a code-base, I spotted a class that addresses the common "requiring objects on heap" problem by having a protected destructor. The class is also abstract, allowing a single instance creation method through a static function.
You can see a reduced version of the pattern below. I have a question regarding the return type of ::Make
:
// header file --------------------------------------
namespace ooo {
class Object {
public:
Object() { std::cout << "Constructor call\n"; }
virtual void F() = 0;
static std::unique_ptr<Object, void (*)(Object *)> Make();
protected:
virtual ~Object() { std::cout << "Destructor call\n"; }
};
} // namespace ooo
// implementation file ----------------------------
namespace {
class ObjectImpl : public ooo::Object {
public:
void F() override { std::cout << "doing stuff\n"; }
~ObjectImpl() override { std::cout << "destructor of derived\n"; }
};
} // namespace
namespace ooo {
std::unique_ptr<Object, void (*)(Object *)> Object::Make() {
return std::unique_ptr<Object, void (*)(Object *)>{
new ObjectImpl, [](Object *e) { delete e; }};
}
} // namespace ooo
void Use() {
// The design of class Object, should prevent users from doing the following:
//
// 1. Explicit allocation - creating instance in free store:
// std::unique_ptr<Envelope> ptr{new Envelope}; // compilation error due to
// private destructor.
//
// 2. Plain automatic instance:
// Envelope e; // Compilation error due to private destructor.
//
// 3. Plain free store instance:
// auto ee = new ooo::Object; // You can't have an instance of abstract class.
// This is the use pattern we want to enable
auto e = ooo::Object::Make();
}
int main() {
std::cout << "Entering example\n\n";
Use();
return 0;
}
As you can see, ::Make()
returns a uniqe pointer, whose type has to state explicitly (we can't use automatic return types because users are only expected to include the header).
Since the type of the deleter is a pointer to a function, and we use a lambda as a deleter, won't the unique pointer capture a pointer to a dying object at the end?
As a side note, I believe the lambda is used because due to scope (it's declared inside the static function) it has access to the protected destructor.