In this section, we will define a type that cannot be allocated with new directly and, also, cannot be released again using delete. As this prevents it from being used with smart pointers directly, we perform the necessary little adaptions to instances of unique_ptr and smart_ptr:
- As always, we first include the necessary headers and declare that we use the std namespace by default:
#include <iostream>
#include <memory>
#include <string>
using namespace std;
- Next, we declare a class that has its constructor and destructor declared private. This way, we simulate the problem that we have to access specific functions that create and destroy instances of it:
class Foo
{
string name;
Foo(string n)
: name{n}
{ cout << "CTOR " << name << 'n'; }
~Foo() { cout << "DTOR " << name << 'n';}
- The static methods, create_foo and destroy_foo, then create and destroy the Foo instances. They work with raw pointers. This simulates the situation of a legacy C API, which prevents us from using them with normal shared_ptr pointers directly:
public:
static Foo* create_foo(string s) {
return new Foo{move(s)};
}
static void destroy_foo(Foo *p) { delete p; }
};
- Now, let's make such objects manageable by shared_ptr. We can, of course, put the pointer we get from create_foo into the constructor of a shared pointer. Only the destruction is tricky because the default deleter of shared_ptr would do it wrong. The trick is that we can give shared_ptr a custom deleter. The function signature that a deleter function or callable object needs to have is already the same as that of the destroy_foo function. If the function we need to call for destroying the object is more complicated, we can simply wrap it into a lambda expression:
static shared_ptr<Foo> make_shared_foo(string s)
{
return {Foo::create_foo(move(s)), Foo::destroy_foo};
}
- Note that make_shared_foo returns a usual shared_ptr<Foo> instance because giving it a custom deleter did not change its type. This is because shared_ptr uses virtual function calls to hide such details. Unique pointers do not impose any overhead, which makes the same trick unfeasible for them. Here, we need to change the type of the unique_ptr. As a second template parameter, we give it void (*)(Foo*), which is exactly the type of pointer to the function, destroy_foo:
static unique_ptr<Foo, void (*)(Foo*)> make_unique_foo(string s)
{
return {Foo::create_foo(move(s)), Foo::destroy_foo};
}
- In the main function, we just instantiate both a shared pointer and a unique pointer instance. In the program output, we will see if they are really, correctly, and automatically destroyed:
int main()
{
auto ps (make_shared_foo("shared Foo instance"));
auto pu (make_unique_foo("unique Foo instance"));
}
- Compiling and running the program yields the following output, which is luckily just what we expected:
$ ./legacy_shared_ptr
CTOR shared Foo instance
CTOR unique Foo instance
DTOR unique Foo instance
DTOR shared Foo instance