C++ shared_ptr with custom deleter
Date: 2023-01-13Last modified: 2024-11-02
Table of contents
The purpose of Foo
class is to trace the constructor and destructor
calls.
struct Foo {
int id{0};
Foo(int i = 0) : id{i} { std::cout << "✅ Foo::Foo(" << i << ")\n"; }
~Foo() { std::cout << "❌ Foo::~Foo(), id=" << id << '\n'; }
};
The DeleterFunctor
is a custom functor that will be used to
release the objects when it goes out of context.
struct DeleterFunctor {
void operator()(Foo* p) const {
std::cout << "❌ Call delete from function object. Foo::id=" << p->id
<< '\n';
delete p;
}
};
This function wrap a POSIX fclose
function call. It is used for
trace purpose only.
int myfclose(FILE* stream) {
std::cout << "❌ myfclose called\n";
return fclose(stream);
}
Context block 1
{
std::cout << "1) constructor with no managed object\n";
std::shared_ptr<Foo> sh1;
if (sh1 == nullptr) {
std::cout << "sh1 == nullptr\n";
} else {
std::cout << "sh1.id = " << sh1->id << '\n'; // never called
}
std::cout << "==> Context block 1 is going out of context here\n";
}
Output of context block 1
1) constructor with no managed object
sh1 == nullptr
==> Context block 1 is going out of context here
Context block 2
{
std::cout << "\n2) constructor with object\n";
std::shared_ptr<Foo> sh2(new Foo{10});
std::cout << "sh2.id = " << sh2->id << '\n';
std::cout << "sh2.use_count(): " << sh2.use_count() << '\n';
std::shared_ptr<Foo> sh3(sh2);
std::cout << "sh3.id = " << sh3->id << '\n';
std::cout << "sh2.use_count(): " << sh2.use_count() << '\n';
std::cout << "sh3.use_count(): " << sh3.use_count() << '\n';
sh2->id = 123;
std::cout << "sh3.id = " << sh3->id << '\n';
std::cout << "==> Context block 2 is going out of context here\n";
}
Output of context block 2
2) constructor with object
✅ Foo::Foo(10)
sh2.id = 10
sh2.use_count(): 1
sh3.id = 10
sh2.use_count(): 2
sh3.use_count(): 2
sh3.id = 123
==> Context block 2 is going out of context here
❌ Foo::~Foo(), id=123
Context block 3
{
std::cout << "\n3) constructor with object and deleter\n";
std::shared_ptr<Foo> sh4(new Foo{11}, DeleterFunctor());
std::shared_ptr<Foo> sh5(new Foo{12}, [](auto p) {
std::cout << "Call delete from lambda... p->id=" << p->id << "\n";
delete p;
});
std::cout << "==> Context block 3 is going out of context here\n";
}
Output of context block 3
3) constructor with object and deleter
✅ Foo::Foo(11)
✅ Foo::Foo(12)
==> Context block 3 is going out of context here
Call delete from lambda... p->id=12
❌ Foo::~Foo(), id=12
❌ Call delete from function object. Foo::id=11
❌ Foo::~Foo(), id=11
Context block 4
{
std::cout << "\n4) constructor with pipe object and deleter (POSIX)\n";
// Borrowed from GrUtils::exec()
std::array<char, 128> buffer;
std::string result;
std::shared_ptr<FILE> pipe(popen("ls -l shared_ptr_with_deleter.cpp", "r"),
pclose); // Flawfinder: ignore
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (!feof(pipe.get())) {
if (fgets(buffer.data(), 128, pipe.get()) != nullptr)
result += buffer.data();
}
std::cout << "ls result: " << result << '\n';
std::cout << "==> Context block 4 is going out of context here\n";
}
Output of context block 4
4) constructor with pipe object and deleter (POSIX)
ls result: -rw-r--r-- 1 geraldo geraldo 4838 Jan 27 2024 shared_ptr_with_deleter.cpp
==> Context block 4 is going out of context here
Context block 5
{
std::cout << "\n5) constructor with pipe object and deleter (POSIX)\n";
std::shared_ptr<FILE> file1(fopen("/tmp/test1.txt", "w"), fclose);
std::shared_ptr<FILE> file2(fopen("/tmp/test2.txt", "w"), myfclose);
std::string s{"Some text"};
fwrite(s.c_str(), s.size(), 1, file1.get());
fwrite(s.c_str(), s.size(), 1, file2.get());
std::cout << "==> Context block 5 is going out of context here\n";
}
Output of context block 5
5) constructor with pipe object and deleter (POSIX)
==> Context block 5 is going out of context here
❌ myfclose called
Possible output
1) constructor with no managed object
sh1 == nullptr
==> Context block 1 is going out of context here
2) constructor with object
✅ Foo::Foo(10)
sh2.id = 10
sh2.use_count(): 1
sh3.id = 10
sh2.use_count(): 2
sh3.use_count(): 2
sh3.id = 123
==> Context block 2 is going out of context here
❌ Foo::~Foo(), id=123
3) constructor with object and deleter
✅ Foo::Foo(11)
✅ Foo::Foo(12)
==> Context block 3 is going out of context here
Call delete from lambda... p->id=12
❌ Foo::~Foo(), id=12
❌ Call delete from function object. Foo::id=11
❌ Foo::~Foo(), id=11
4) constructor with pipe object and deleter (POSIX)
ls result: -rw-r--r-- 1 geraldo geraldo 4838 Jan 27 2024 shared_ptr_with_deleter.cpp
==> Context block 4 is going out of context here
5) constructor with pipe object and deleter (POSIX)
==> Context block 5 is going out of context here
❌ myfclose called
Content of /tmp/test1.txt
Some text
Content of /tmp/test2.txt
Some text