Last active
November 2, 2024 05:55
-
-
Save jwpeterson/533e8fa1e6fb866c286ff56f8ca0e9c3 to your computer and use it in GitHub Desktop.
Use non-static member function for std::unique_ptr deleter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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