A new generic pointer any_ptr (now dumb_ptr) to make code more reusable among smart pointers
I have been using a lot of different boost smart pointers lately, as well as normal pointers. I have noticed that as you develop you tend to realise that you have to switch pointer types开发者_Python百科 and memory management mechanism because you overlooks some circular dependency or some othe small annoying thing. When this happens and you change your pointer type you have to either go and change a whole bunch of you method signatures to take the new pointer type, or at each call site you have to convert between pointer types. You also have a problem if you have the same function but want it to take multiple pointer types.
I am wondering if there already exists a generic way to deal with, i.e. write methods that are agnostic of the pointer type you pass to it?
Obviously i can see a few ways to do this, one is to write overloaded methods for each pointer type, but this quickly becomes a hassle. The other is to use a template style solution with some type inference, but this will cause some significant bloat in the compiled code and is likely to start throwing strange unsolvable template errors.
My idea is to write a new class any_ptr<T>
with conversion constructors from all the major pointer types, say, T*
, shared_ptr<T>
, auto_ptr<T>
, scoped_ptr<T>
perhaps even weak_ptr<T>
and then have it expose the *
and ->
operators. In this way it could be used in any function that does not return the pointer outside of the function and could be called with any combination of common pointer types.
My question is whether this is a a really stupid thing to do? I see that it could be abused, but assuming it is never used for functions that return the any_ptr, is there a major problem I would be walking into? Your thoughts please.
EDIT 1
Having read your answers I would like to make some notes that were too long for the comments.
Firstly with regards to using raw pointers or references (@shoosh). I agree that you could make the functions use raw pointers, but then assume the case where I was using shared_ptr which means I would have had to go ptr.get() at each call site, now assume I realize I made a cyclic reference and I have to change the pointer to a weak_ptr then I have to go and change all those call sites to x.lock().get() . Now I agree this is not a catastrophe but it is irritating and I feel there is an elegant solution to this. The same could be said for passing the as T& references and going *x, similar call site changes would have to be made.
What I trying to do here is make the code more elegant to read and easier to refactor even through large changes in pointer type.
Secondly with regards to smart_ptr semantics: I agree that different smart pointers are used for different reasons , and have certain considerations that must be taken care of with regards to copying and storage (this is why boost::shared_ptr<T>
is not automatically convertible to a T*
).
However I envisioned any_ptr (maybe a bad name in retrospect) to only be used in cases where the pointer would not be stored (in anything other than perhaps temporary variables on the stack). It should just be implicitly constructible from the various smart pointer types, overload the * and -> operators and be convertible to a T* (through a custom conversion function T*()
). In this way the semantics of any_ptr
are the exact same as as T*
. And as such it should only be used in places where it would be safe to use a raw ptr (This is what @ Alexandre_C was saying in a comment). This also means that there would be none of the "heavy machinery" that @Matthieu_M was talking about.
Thirdly with regards to templates. While templates are great for somethings I am wary of them for the reasons I have made above.
FINALLY: So basically what I am trying to do is for functions where one would normally use raw ptr's (T*) as the parameters I would like to create a system where those parameters can automatically accept any of the various smart_ptr types without having to do the conversions at the call sites. The reason I want to do this is because I think it will make the code more readable by eliminating conversion cruft (and hence also slightly shorter, though not by much) and it would make refactoring and trying different smart pointer regimes less of a hassle.
Perhaps I should have called it unmanaged_ptr
instead of any_ptr
. That would more correctly describe the semantics. I apologize for the crummy name.
EDIT 2
Okay so here is the class I had in mind. I have called it dumb_ptr.
template<typename T>
class dumb_ptr {
public:
dumb_ptr(const dumb_ptr<T> & dm_ptr) : raw_ptr(dm_ptr.raw_ptr) { }
dumb_ptr(T* raw_ptr) : raw_ptr(raw_ptr) { }
dumb_ptr(const boost::shared_ptr<T> & sh_ptr) : raw_ptr(sh_ptr.get()) { }
dumb_ptr(const boost::weak_ptr<T> & wk_ptr) : raw_ptr(wk_ptr.lock().get()) { }
dumb_ptr(const boost::scoped_ptr<T> & sc_ptr) : raw_ptr(sc_ptr.get()) { }
dumb_ptr(const std::auto_ptr<T> & au_ptr) : raw_ptr(au_ptr.get()) { }
T& operator*() { return *raw_ptr; }
T * operator->() { return raw_ptr; }
operator T*() { return raw_ptr; }
private:
dumb_ptr() { }
dumb_ptr<T> operator=(const dumb_ptr<T> & x) { }
T* raw_ptr;
};
It can convert from the common smart pointers automatically and can be treated as a raw T* pointer, further it can be converted automatically to a T*. The default constructor and the assignment operator (=) have been hidden to deter people from using it for anything other than function arguments. When used as a function argument the following can be done.
void some_fn(dumb_ptr<A> ptr) {
B = ptr->b;
A a = *ptr;
A* raw = ptr;
ptr==raw;
ptr+1;
}
Which is pretty much everything you would want to do with a pointer. It has the exact same semantics as a raw pointer T*
. But now it can be used with any smart pointer as the parameter without having to repeat the conversion code (.get,.lock) at each call site. Also if you change your smart pointers you don't have to go around fixing each call site.
Now I think this is reasonably useful, and I can't see problems with it?
With such a class any_ptr
, you will not be able to do practically anything other than *
and ->
. No assignment, copy construction, duplication or destruction. if that's all you need then just write the function taking as argument the raw pointer T*
and then call it use .get()
or whatnot on your automatic pointer.
You can use different smart pointers because they offer different semantics and tradeoffs.
What would be the semantics of any_ptr
? If it comes from unique_ptr
/ scoped_ptr
it should not be copied. If it comes from shared_ptr
or weak_ptr
it would need their heavy machinery for reference counting.
You would have a type with all the disadvantages (heavy, no copyable) for little gain...
What you have here is a problem of interface. Unless the methods manage the lifetime of the object (in which case the exact pointer type is required), then it need not be aware of how this lifetime is managed.
This means that methods that merely act on the object should take either a pointer or a reference (depending on whether it might be null), and be called accordingly:
void foo(T* ptr);
which is calledshared_ptr<T> p; foo(p.get());
void foo(T& ptr);
which is calledfoo(*p);
Note: the second interface is much more pointer agnostic
This is plain encapsulation: a method need be aware only of the bare minimum it needs to operate. If it does not manage the lifetime, then exposing how this lifetime is managed to the method... causes those ripples that you've witnessed.
If your methods really have to be smart pointer-agnostic, why not pass a garden variety pointer:
virtual void MyMethod(MyClass* ptr)
{
ptr->doSomething();
}
and do
MyObj->MyMethod(mySmartPtr.get());
at the call site ?
You can even pass a reference:
virtual void MyMethod(const MyClass& ptr)
{
ptr->doSomething();
}
and do
MyObj->MyMethod(*mySmartPointer);
which has the advantage to have pretty much always the same syntax (except for std::weak_ptr
).
Otherwise, smart pointer types are becoming standard: std::unique_ptr
, std::shared_ptr
and std::weak_ptr
have their use.
Smart pointers are for storing objects according to certain resource management semantics. You can pretty much always extract a bare pointer from them.
Methods should therefore in general expect bare pointers, or specific kinds of smart pointers in case they need to. For instance, you can pass a shared_ptr
by const reference in order to store a shared handle to an object.
To this goal, you can add typedefs:
struct MyClass
{
typedef std::shared_ptr<MyClass> Handle;
static Handle CreateHandle(...);
...
private:
Handle internalHandle;
void setHandle(const MyClass::Handle& h);
};
and do
void MyClass::setHandle(const MyClass::Handle& h)
{
this->internalHandle = h;
}
When this happens and you change your pointer type you have to either go and change a whole bunch of you method signatures to take the new pointer type, or at each call site you have to convert between pointer types. You also have a problem if you have the same function but want it to take multiple pointer types.
I am wondering if there already exists a generic way to deal with, i.e. write methods that are agnostic of the pointer type you pass to it?
You almost answered your own question.
A smart-pointer should be present in function signatures only if ownership of an object is being transferred. If a function does not take ownership of an object, it should accept it by plain pointer or reference. Only functions that need to own an object or extend its lifetime should accept it by smart pointer.
Answering my own question so it doesn't appear in the unanswered list. Basically none of the other answers found what I considered to be a serious flaw in my idea. Props to @Alexandre C. for understanding what I was trying to do.
精彩评论