Assume that I want to write function that takes in a pointer. However I want to allow caller to use naked pointers or smart pointers - whatever they prefer. This should be good because my code should rely on pointer semantics, not how pointers are actually implemented. This is one way to do this:
template<typename MyPtr>
void doSomething(MyPtr p)
{
//store pointer for later use
this->var1 = p;
//do something here
}
Above will use duck typing and one can pass naked pointers or smart pointers. The problem occurs when passed value is base pointer and we need to see if we can cast to derived type.
template<typename BasePtr, typename DerivedPtr>
void doSomething(BasePtr b)
{
auto d = dynamic_cast<DerivedPtr>(b);
if (d) {
this->var1 = d;
//do some more things here
}
}
Above code will work for raw pointers but won't work for the smart pointers because I need to use dynamic_pointer_cast
instead of dynamic_cast
.
One solution to above problem is that I add new utility method, something like, universal_dynamic_cast
that works both on raw pointers and smart pointers by selecting overloaded version using std::enable_if
.
The questions I have are,
shared_ptr
in our library public APIs? I know this depends on purpose of library, but what is the general feeling about using shared_ptr
all over API signatures? Assume that we only have to support C++11.shared_ptr<MyBase>
which would sacrifice flexibility for callers to pass whatever wrapped in whatever pointer but reader of my code would be more confident and can build better model on on what should be coming in. In C++ public library APIs, are there general preferences/advantages one way or another?template<typename T>
and let caller decide if T is some pointer type or reference or class. This super generic approach obviously don't work if I have to call something in T because C++ requires dereferencing pointer types which means I have to probably create utility method like universal_deref
using std::enable_if
that applies * operator to pointer types but does nothing for plain objects. I wonder if there are any design patterns that allows this super generic approach more easily. Again, above all, is it worth going all these troubles or just keep thing simple and use shared_ptr
everywhere?To store a shared_ptr
within a class has a semantic meaning. It means that the class is now claiming ownership of that object: the responsibility for its destruction. In the case of shared_ptr
, you are potentially sharing that responsibility with other code.
To store a naked T*
... well, that has no clear meaning. The Core C++ Guidelines tell us that naked pointers should not be used to represent object ownership, but other people do different things.
Under the core guidelines, what you are talking about is a function that may or may not claim ownership of an object, based on how the user calls it. I would say that you have a very confused interface. Ownership semantics are usually part of the fundamental structure of code. A function either takes ownership or it does not; it's not something that gets determined based on where it gets called.
However, there are times (typically for optimization reasons) where you might need this. Where you might have an object that in one instance is given ownership of memory and in another instance is not. This typically crops up with strings, where some users will allocate a string that you should clean up, and other users will get the string from static data (like a literal), so you don't clean it up.
In those cases, I would say that you should develop a smart pointer type which has this specific semantics. It can be constructed from a shared_ptr<T>
or a T*
. Internally, it would probably use a variant<shared_ptr<T>, T*>
or a similar type if you don't have access to variant
.
Then you could give it its own dynamic/static/reinterpret/const_pointer_cast
functions, which would forward the operation as needed, based on the status of the internal variant
.
Alternatively, shared_ptr
instances can be given a deleter object that does nothing. So if your interface just uses shared_ptr
, the user can choose to pass an object that it technically does not truly own.
The usual solution is
template<typename T>
void doSomething(T& p)
{
//store reference for later use
this->var1 = &p;
}
This decouples the type I use internally from the representation used by the caller. Yes, there's a lifetime issue, but that's unavoidable. I cannot enforce a lifetime policy on my caller and at the same time accept any pointer. If I want to ensure the object stays alive, I must change the interface to std::shared_ptr<T>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With