Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why and when do I need to supply my own deleter?

Why and when do I need to supply my own deleter? Isn't keyword delete sufficient enough?

If you use a smart pointer to manage a resource other than memory allocated by new, remember to pass a deleter.


Update:

As being asked in comments, the reason why I am unclear about the quoted text and example is that I was thinking wrong about something, which it's that I had been thinking of smart pointer is only invented for/related to dynamic memory management. So the example uses smart pointer to manage a non-dynamic memory stuff makes me confused.

A good explanation from a senior:

The smart pointer doesn't care at all about something being dynamic memory as such. It's just a way to keep track of something while you need it, and destroy that something when it goes out of scope. The point of mentioning file handles, network connections, etc., was to point out that they're not dynamic memory, but a smart pointer can manage them just fine anyway.


C++ Primer 5th is taking a pseudo network connection (do not define destructors) to illustrate.

Bad:

struct destination; // represents what we are connecting to
struct connection; // information needed to use the connection
connection connect(destination*); // open the connection
void disconnect(connection); // close the given connection
void f(destination &d /* other parameters */)
{
// get a connection; must remember to close it when done
connection c = connect(&d);
// use the connection
// if we forget to call disconnect before exiting f, there will be no way to closes
}

Good:

void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other parameters */)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}

Full context screenshot (I clear some unrelated text):
Smart Pointers and Dumb classes

Smart Pointers and Dumb classes part 2

like image 851
Rick Avatar asked Jul 11 '18 05:07

Rick


People also ask

Do I need to delete shared pointer?

So no, you shouldn't. The purpose of shared_ptr is to manage an object that no one "person" has the right or responsibility to delete, because there could be others sharing ownership.

Should I delete Unique_ptr?

An explicit delete for a unique_ptr would be reset() . But do remember that unique_ptr are there so that you don't have to manage directly the memory they hold. That is, you should know that a unique_ptr will safely delete its underlying raw pointer once it goes out of scope.

What is delete function C++?

Delete is an operator that is used to destroy array and non-array(pointer) objects which are created by new expression. Delete can be used by either using Delete operator or Delete [ ] operator. New operator is used for dynamic memory allocation which puts variables on heap memory.

How do I release a shared pointer?

As far as I can see, it is simply not possible to detach a shared_ptr. You will need to either use another class or come up with a design where you do not need to detach it. And I should read the dupes as you can actually hack it together using a very special deleter. I actually just solved this after reading this....


1 Answers

You need to supply your own delete to the smart pointer creation when the standard delete is not appropriate for deallocating, releasing, discarding, or otherwise disposing of a resource whose life time is being governed by the smart pointer.

A typical use of a smart pointer is to allocate memory as the resource being managed by the smart pointer so that when the smart pointer goes out of scope, the resource being managed, in this case memory, is discarded by using the delete operator.

The standard delete operator does two things: (1) call the object's destructor to allow the object to do any cleanup it needs to do before it's allocated memory is released or deallocated and (2) releases the memory that was allocated by the standard new operator for the object when it was constructed. This is the reverse order of what happens with the new operator which (1) allocates memory for the object and does the basic initialization needed to establish the construction environment for the object and (2) calls the constructor of the object to create the object's starting state. See What does the C++ new operator do other than allocation and a ctor call?

So the key question for needing your own deleter is "what actions that were done before the object constructor was invoked need to be unwound and backed out after the object's destructor completes?".

Normally this is a memory allocation of some kind such as is done by the standard new operator.

However in the case of some resource other than memory allocated with the new operator, the use of the delete operator is not appropriate since the resource is not memory that was allocated by using the new operator.

So when using a smart pointer for this kind of resource where the delete operator is not appropriate, you need to supply your own deleter method or function or operator which the smart pointer will use when it goes out of scope and triggers its own destructor which will in turn handle the discarding of any resources that are being managed by the smart pointer.

A simple example with output

I put together a simple example with std::unique_ptr<> along with the output generated to show using and not using a deleter with the pointer as well as showing explicit use of the destructor.

The source code of a simple Windows console application looks like:

// ConsoleSmartPointer.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

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

class Fred {
public:
    Fred() { std::cout << "  Fred Constructor called." << std::endl; }
    ~Fred() { std::cout << "  Fred Destructor called." << std::endl; }
};
class George {
public:
    George() { std::cout << "   George Constructor called" << std::endl; }
    ~George() { std::cout << "   George Destructor called" << std::endl; }
private:
    int iSomeData;
    std::string  a_label;
    Fred  myFred;
};

void cleanupGeorge(George *)
{
    // just write out a log and do not explicitly call the object destructor.
    std::cout << "  cleanupGeorge() called" << std::endl;
}

void cleanupGeorge2(George *x)
{
    // write out our message and then explicitly call the destructor for our
    // object that we are the deleter for.
    std::cout << "  cleanupGeorge2() called" << std::endl;
    x->~George();    // explicitly call destructor to do cleanup.
}

int func1()
{
    // create a unique_ptr<> that does not have a deleter.
    std::cout << "func1 start. No deleter." << std::endl;

    std::unique_ptr<George> p(new George);

    std::cout << "func1 end." << std::endl;
    return 0;
}

int func2()
{
    // create a unique_ptr<> with a deleter that will not explicitly call the destructor of the
    // object created.
    std::cout << "func2 start. Special deleter, no explicit destructor call." << std::endl;

    std::unique_ptr<George, void(*)(George *)> p(new George, cleanupGeorge);

    std::cout << "func2 end." << std::endl;
    return 0;
}

int func3()
{
    // create a unique_ptr<> with a deleter that will trigger the destructor of the
    // object created.
    std::cout << "func3 start. Special deleter, explicit destructor call in deleter." << std::endl;

    std::unique_ptr<George, void(*)(George *)> p(new George, cleanupGeorge2);

    std::cout << "func3 end." << std::endl;
    return 0;
}

int main()
{
    func1();
    func2();
    func3();
    return 0;
}

The above simple application generates the following output:

func1 start. No deleter.
  Fred Constructor called.
   George Constructor called
func1 end.
   George Destructor called
  Fred Destructor called.
func2 start. Special deleter, no explicit destructor call.
  Fred Constructor called.
   George Constructor called
func2 end.
  cleanupGeorge() called
func3 start. Special deleter, explicit destructor call in deleter.
  Fred Constructor called.
   George Constructor called
func3 end.
  cleanupGeorge2() called
   George Destructor called
  Fred Destructor called.

Additional posts

What is a smart pointer and when should I use one?

Using custom deleter with std::shared_ptr

See as well this discussion about deleter with std::make_shared<> and why it isn't available. How to pass deleter to make_shared?

Is custom deleter for std::unique_ptr a valid place for manual call to destructor?

When does std::unique_ptr<A> need a special deleter if A has a destructor?

RAII and smart pointers in C++

like image 148
Richard Chambers Avatar answered Sep 26 '22 02:09

Richard Chambers