Skip to content

Instantly share code, notes, and snippets.

@jwpeterson
Last active May 11, 2022 19:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jwpeterson/533e8fa1e6fb866c286ff56f8ca0e9c3 to your computer and use it in GitHub Desktop.
Save jwpeterson/533e8fa1e6fb866c286ff56f8ca0e9c3 to your computer and use it in GitHub Desktop.
Use non-static member function for std::unique_ptr deleter
// Example of how a non-static member function can be used as the "deleter" for a std::unique_ptr via std::bind.
// Note: this approach incurs an additional overhead per unique_ptr object to store a pointer to the deleter
// function, so it may not be appropriate in cases where zero-overhead unique_pts are required.
// Source: https://stackoverflow.com/questions/29525370/how-to-pass-a-non-static-member-function-as-a-unique-ptr-deleter
#include <memory>
#include <iostream>
#include <functional>
#include <exception>
/**
* In this variation on the original,
* 1. The deleter uses the value of a member variable
* 2. The take_ownership() API sets _is_shallow_copy == false and takes responsibility for deleting the pointer
* 3. The hold_pointer() API sets _is_shallow_copy == true and skips deleting the pointer
*/
class Foo
{
public:
Foo() :
_is_shallow_copy(false)
{}
~Foo() = default;
void print() const
{
if (_up)
std::cout << *_up << std::endl;
else
std::cout << "No value" << std::endl;
}
/**
* Resets _up so that we are no longer a shallow (or deep) copy of
* anything. The deleter is called (or not) depending on the value
* of the _is_shallow_copy flag.
*/
void clear()
{
_up.reset();
_is_shallow_copy = false;
}
void take_ownership(std::unique_ptr<int> input)
{
this->set_pointer(input.release());
_is_shallow_copy = false; // now a deep copy
}
void hold_pointer(int * input)
{
this->set_pointer(input);
_is_shallow_copy = true; // now a shallow copy
}
void swap_pointer(std::unique_ptr<int> & input)
{
if (!_up || !_is_shallow_copy)
{
// The "input" unique_ptr does not have the same deleter as we do, so they cannot be
// swapped directly
// _up.swap(input); // error: no known conversion from 'std::unique_ptr<int>' to 'std::unique_ptr<int, std::function<void(int*)> >&'
// Release ownership of both (does not call deleter)
int * ours = _up.release();
int * theirs = input.release();
// Swap.
this->set_pointer(theirs);
input.reset(ours);
// We either still aren't a shallow copy, or we aren't one now
_is_shallow_copy = false;
}
else
{
// Since "input" is an "owning" pointer, if we are currently a
// shallow copy we don't want "input" to own the pointer we are
// currently holding, since in that case it might be
// double-deleted by both input and from its original scope.
throw std::runtime_error("Can't swap with unique_ptr, currently a shallow copy.");
}
}
void swap_pointer(int * & input)
{
if (!_up || _is_shallow_copy)
{
// We either already are a shallow copy or we can become one.
// Release "ownership" of our shallow copy
int * ours = _up.release();
// Swap.
this->set_pointer(input);
input = ours;
// We either were a shallow copy before or we are now
_is_shallow_copy = true;
}
else // !_is_shallow_copy and _up is set
{
// If we are not a shallow copy, then we do not want to give
// away our ownership to a dumb pointer, because it may be leaked.
throw std::runtime_error("Can't swap with dumb pointer, currently a deep copy.");
}
}
private:
/**
* Deletes the managed resource or doesn't depending on the value of
* the _is_shallow_copy flag.
*/
void deleter(int* p)
{
if (!_is_shallow_copy)
{
delete p;
std::cout << "In deleter, deleting" << std::endl;
}
else
std::cout << "In deleter, not deleting" << std::endl;
}
/**
* Implementation for take_ownership() and hold_pointer(), so we do
* not repeat complicated std::bind code. Note: std::unique_ptr::operator=
* calls the deleter() with the current value of _is_shallow_copy to clean
* up any resource it might currently own.
*/
void set_pointer(int * input)
{
_up = std::unique_ptr<int, std::function<void(int*)>>(input, std::bind(&Foo::deleter, this, std::placeholders::_1));
}
bool _is_shallow_copy;
std::unique_ptr<int, std::function<void(int*)>> _up;
};
int main()
{
// foo starts off _up.get() == nullptr and can become either a shallow or deep copy initially
Foo foo;
// A stack pointer that will be used in some tests
int stack = 6;
int * stack_pointer = &stack;
// OK, foo is not a shallow or deep copy yet
foo.swap_pointer(stack_pointer);
std::cout << "stack_pointer = " << stack_pointer << " (should be 0)" << std::endl;
std::cout << "foo.print() (should be 6) = ";
foo.print();
// foo no longer manages anything. The pointer that it had to the
// stack value of 6 is now lost.
foo.clear();
// After the swap, "five" won't manage anything (it will be set to nullptr)
// The swap is allowed because foo was just cleared.
auto five = std::make_unique<int>(5);
foo.swap_pointer(five);
std::cout << "five.get() (should be 0) = " << five.get() << std::endl;
std::cout << "foo.print() (should be 5) = ";
foo.print();
// Create resource, let foo take ownership
auto donor = std::make_unique<int>(42);
foo.take_ownership(std::move(donor));
// foo is a deep copy, so it should not accept being swapped for a dumb pointer
try
{
foo.swap_pointer(stack_pointer);
}
catch (std::exception & e)
{
std::cout << "Handled exception: " << e.what() << std::endl;
}
// Now make foo into a shallow copy. delete is called for the
// existing resource, so nothing is leaked.
int loaner = 5;
foo.hold_pointer(&loaner);
// foo is now a shallow copy, let's swap with it for a stack pointer (this is allowed)
foo.swap_pointer(stack_pointer);
std::cout << "*stack_pointer = " << *stack_pointer << " (should be 5)" << std::endl;
// foo is a shallow copy, so it should not accept being swapped for a managed pointer
try
{
auto managed = std::make_unique<int>(99);
foo.swap_pointer(managed);
}
catch (std::exception & e)
{
std::cout << "Handled exception: " << e.what() << std::endl;
}
// make foo a deep copy again by having it take ownership
foo.take_ownership(std::make_unique<int>(43));
// swap with donor2, check that donor2's value is 43
auto donor2 = std::make_unique<int>(44);
foo.swap_pointer(donor2);
std::cout << "*donor2 = " << *donor2 << " (should be 43)" << std::endl;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment