🎓 A1Learner
🔍
0 of 28 lessons completed

C++ Custom Deleters

A custom deleter is a cleanup step that a smart pointer runs when it destroys the value it owns. The deleter is named in the pointer type and runs when the pointer leaves scope.

A deleter that prints at scope exit

The CloseFile deleter prints its line when the scope ends. The line after the scope prints second, and the order proves when the deleter ran.

#include <iostream>
#include <memory>

struct CloseFile {
  void operator()(int* p) const {
    std::cout << "Closing file\n";
    delete p;
  }
};

int main() {
  {
    std::unique_ptr<int, CloseFile> file(new int(42));
    std::cout << "Value: " << *file << "\n";
  }
  std::cout << "After scope\n";
  return 0;
}

A lambda as the deleter

A lambda can serve as the deleter. Its type is named with decltype inside the pointer type.

#include <iostream>
#include <memory>

int main() {
  auto closer = [](int* p) {
    std::cout << "Lambda closing\n";
    delete p;
  };
  {
    std::unique_ptr<int, decltype(closer)> file(new int(7), closer);
    std::cout << "Value: " << *file << "\n";
  }
  std::cout << "After scope\n";
  return 0;
}

A deleter with a label

The NamedCloser deleter holds a label string. The two pointers are destroyed in reverse order of creation, and the closing lines follow that order.

#include <iostream>
#include <memory>
#include <string>

struct NamedCloser {
  std::string label;
  void operator()(int* p) const {
    std::cout << "Closing " << label << "\n";
    delete p;
  }
};

int main() {
  {
    std::unique_ptr<int, NamedCloser> first(new int(1), NamedCloser{"alpha"});
    std::unique_ptr<int, NamedCloser> second(new int(2), NamedCloser{"beta"});
    std::cout << "Using: " << *first << " and " << *second << "\n";
  }
  std::cout << "After scope\n";
  return 0;
}

Example

#include <iostream>
#include <memory>
#include <string>

struct TableCloser {
  std::string name;
  void operator()(int* p) const {
    std::cout << "Closed " << name << "\n";
    delete p;
  }
};

int main() {
  {
    std::unique_ptr<int, TableCloser> report(new int(10), TableCloser{"report"});
    std::cout << "Opened report\n";
    std::unique_ptr<int, TableCloser> log(new int(20), TableCloser{"log"});
    std::cout << "Opened log\n";
    std::cout << "Report rows: " << *report << "\n";
    std::cout << "Log rows: " << *log << "\n";
  }
  std::cout << "Table done\n";
  return 0;
}

Key points

  • A std::unique_ptr destroys its value with delete by default.
  • A custom deleter replaces the default cleanup with a written step.
  • The deleter type is written as the second type inside the angle brackets of the pointer.
  • The deleter runs at the moment the owning pointer leaves scope.
  • Pointers are destroyed in reverse order of creation, so their deleters run in that order too.
  • A deleter can carry state, such as a label it prints while closing.

In this example, two handles are opened and used inside one scope. The scope ends, and the deleters close the log handle first and the report handle second. The final line prints after both closings.

Note

A lambda has no written type name, so decltype names its type inside the pointer.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

When does a custom deleter run?