Back to TILs

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

References