Reputation: 960
I declare a type like
template <typename T>
using SmartPtr = std::unique_ptr<T, MyDeleter>;
In some specific cases, i wanted to count references to an object and conditionally delete it when the pointer will be out of scope.
I know there is std::shared_ptr class but I have some objects I want to access through a common interface. Some of those objects are owned by their class, other classes have a factory method that yield the ownership of the objects they create.
My questions are:
SmartPtr
have an own, personal MyDeleter
object?std::unique_ptr
s pointing to the same object considered a bad coding practice? Even though I use a custom deleter?It feels wrong to me to use shared_ptr without a deleter because the objects owned by class can't be anyway deleted because they wasn't created through new. They are part of an array, member of the class. Not all the objects pointed by SmartPtr was created using new operator.
Upvotes: 1
Views: 382
Reputation: 275405
This is a working "maybe deletes, maybe not" class:
enum class should_delete {
yes,
no,
};
struct maybe_delete {
should_delete choice = should_delete::yes;
template<class T>
void operator()(T* t){
if (choice!=should_delete::no)
delete t;
}
maybe_delete() = default;
maybe_delete(should_delete c):choice(c) {}
maybe_delete(maybe_delete const&)=default;
// move in terms of copy, then clear state to default:
maybe_delete(maybe_delete&& o):maybe_delete(o){
o.choice = should_delete::yes;
}
};
template<class T>
using maybe_unique_ptr = std::unique_ptr<T, maybe_delete>;
is a type that might be a unique pointer.
Construct with {ptr, should_delete::no}
in order to have the pointer not delete the object in question. (moved) copies of such a maybe_unique_ptr
will have the proper state.
Note that .reset(ptr)
can be dangerous, because the object gets the deletion state of what was there before. I ensured that moved-from maybe_unique_ptr
s have the same state as trivially constructed ones with the careful maybe_delete
move ctor.
That being said, if you want to write an intrusive smart pointer, I wouldn't do it as a raw unique_ptr
.
If you look at std::shared_ptr
, they have the "god mode" constructor:
shared_ptr<T>::shared_ptr( T*, shared_ptr<U> )
which lets you have a shared-ptr to T
that forwards all reference counting to a completely different shared-ptr. This is intended for "member" shared-ptrs (among other uses) -- if you have a structure X
with a member Y
, you can have a shared-ptr-to Y
that actually reference counts the enclosing X
structure.
For an intrusive smart pointer, I'd write a smart pointer. It would store a T*
and a "scope guard" type object for cleanup that does the decrement (the scope guard could even be a unique_ptr
to a reference counting interface with a destroyer that decrements). Do the boilerplate for a smart pointer (a few dozen lines). On copy, clone the scope guard and the T*
. On move, move the pointer and scope guard (and clear the source).
Have constructors that wrap a raw T*
with a null ref_count
(if needed), or a T*
and a ref_count
interface, or both-as-one.
If the containing objects have a ref count, I'd be tempted to use that, just to early detect shutdown issues.
bonus version with arbitrary deleter guarded by maybe_delete
. That would allow you to refcount decrement conditionally, for example, without mixing the two operations too much. Uses empty base class optimization to prevent wasted space (as std::default_delete
is empty).
Upvotes: 2
Reputation: 66922
You want to have a pointer member that may or may not own the thing it points at? Easy, seperation of concerns:
std::unique_ptr<T> maybe_owning;
T* always_pointing;
constructor(const T& copy_and_own)
:maybe_owning( new T(copy_and_own))
,always_pointing(maybe_owning.get())
{}
constructor(T* just_reference)
:maybe_owning()
,always_pointing(just_reference)
{}
void do_task() {
always_pointing->thing();
}
You have a single pointer that always points to the data, and doesn't care about ownership in the slightest.
You also have a smart pointer that can conditionally be used to own.
All problems solved, with no crazy code.
I use this commonly actually, an fstream
that may or may not open a file, and a istream&
which sometimes connects to the fstream
and sometimes to cin
, then I can read from either source the same.
Upvotes: 3