Greg Domjan
Greg Domjan

Reputation: 14125

Is there a way to change the delete action on an existing instance of shared_ptr

I have a function where I want a cleanup action done 90% of the time, but in 10% I want some other action to be done.

Is there some way to use some standard scoped control likeshared_ptr<> so that initially it can have one delete action and then later in the function the delete action can be changed?

shared_ptr<T> ptr( new T, std::mem_fun_ref(&T::deleteMe) );
ptr.pn.d = std::mem_fun_ref(&T::queueMe);

Upvotes: 12

Views: 3570

Answers (5)

smitsyn
smitsyn

Reputation: 732

Not really - the standard for shared_ptr is written in such a way that the Deleter may be stored by value in control node (a special object that contains the reference counter, holds deleter, tracks weak pointers etc). The deleter is type-erased, but if you know the concrete deleter type somehow, you can use std::get_deleter<Deleter>(ptr). With it you may access the deleter and change its state. Example:

struct A {};

struct deleter {
    void operator()(A* a) {delete a; }
    int m_state;
};

std::shared_ptr<A> ptr(new A(), deleter{});

std::get_deleter<deleter>(ptr)->m_state = 5;

And if you use just a function pointer for all deleters, then yes you can completely replace it, as all potential deleters use the same signature.

(Yes I know the question is 9 years old, but I've just faced this problem in 2020 and solved it like this. The possible reason for it is wrapping C pointers and objects from legacy code that manage ownership through raw pointers)

Upvotes: 13

mewo1234
mewo1234

Reputation: 319

#include <iostream>
#include <memory>
#include <functional>

struct A {
    ~A() {
        std::cout << "~A()" << std::endl;
    }
};

using DeleterCb = std::function<void(A* p)>;

struct ADeleter {
 public:
    explicit ADeleter(DeleterCb cb) :
        mDeleterCb(cb) {}
    ADeleter() = delete;
    ~ADeleter() = default;

    void operator()(A *a) {
        mDeleterCb(a);
    }
    
    void setDeleterCb(DeleterCb cb) {
        mDeleterCb = cb;
    }

 private:
    DeleterCb mDeleterCb;
};

int main() {
    auto sp = std::shared_ptr<A>(new A{},
        ADeleter([](A *p){
            delete p;
            std::cout << "deleter_1" << std::endl;
        })
    );
    
    std::get_deleter<ADeleter>(sp)->setDeleterCb(
        [](A *p){
            delete p;
            std::cout << "deleter_2" << std::endl;
        }
    );
}

Upvotes: 1

This doesn't make any sense, since there is any number of shared_ptrs managing the ownership of the value. You'd need to modify them all, and that's not feasible. Let's not forget that a control block is an implementation detail, so going "aha, but change it in the control block" won't work.

The delete actions should be controlled by the instance owned by shared_ptr, e.g.

class C {
...
  void (C::action*)() { &C::action1 };
  void action1();
  void action2();
  ~C() { (this->*action)(); }
};

void test() {
  std::shared_ptr<C> a;
  a->action = &C::action2;
  // action2 gets invoked once `a` falls out of scope
}

Upvotes: 0

John Sampson
John Sampson

Reputation: 574

There is a valid reason to need to change the deleter. Take this for example:

int foo( std::shared_ptr<double>& bar ) {
    ...
    std::shared_ptr<double> p( my_allocator<double>::allocate(), my_deleter<double>() );
    bar.swap(p); // this copies the deleter
    ...
}

int main( int, char** ) {
    std::shared_ptr<double> d;
    foo( d ); // d now has a new deleter that will be called when it goes out of scope
    ...
}

In this case the foo() function allocates a double* using some special allocator. It needs to free that memory in a special way also. The caller shouldn't need to know how to free the memory.

Upvotes: 2

ereOn
ereOn

Reputation: 55816

I don't think you can change the deleter once the shared_ptr was created.

But why would you do that ? Usually, when you create an object, you know immediatly how it must be destroyed. This is not likely to change.

If you really must do some specific treatments, you still can provide a custom deleter which does special things depending on the required logic.

Upvotes: 2

Related Questions