Friday, March 18, 2011

dumb_ptr, a smart_ptr conversion class in C++ (only for programmers)

Everyone reading this who is not a programmer, 
STOP READING NOW!!
Seriously this post will make no sense to you, do something you will enjoy like going swimming. For those who are programmers this may interest you.

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 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 was wondering if there already existed a generic way to deal with this, i.e. write methods that are agnostic of the pointer type you pass to it?
The obvious answer is write all your simple methods (methods that don't manipulate ownership of the pointer and don't return it to anything) to take raw pointers (T*) or references (T&) as parameters. But this still means that you have to write code at each call site to extract the raw pointer, and if you change the type of smart pointers you are using then you have to change code at every call site. That's a hassle, I like to avoid hassles.

You could also overload your methods for different pointer types, but thats a huge waste of time and makes the more code difficult to read. Another way is to use a template style solution with some type inference, but this would cause some significant bloat in the compiled code and is likely to start throwing strange unsolvable template errors.

My idea was 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>  and 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.

The class ended up being really simple, here it is:

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.

So I think this is a pretty neat solution. The idea and most of the content for this post come from a Stackoverflow question I asked, where I presented this idea and asked if anyone saw any major problems with it. The answering parties didn't think much of it, but I think they missed the point (No offence intended) . The point is that it should be less of a hassle to program, even in languages like C++.

Well that's it, I hope someone finds it useful

3 comments:

taliesin said...

Neat. I was always weary about using Boost pointers because of this precise problem. But one question: where's the destructor? It needs to take ownership of non-dumb pointers at least until its done, doesn't it?

James said...

No it does not take owner ship, the semantics are the same as if you used a raw pointer, which doesn't take ownership. All pointer management is left outside of this pointer class and as such it should only be used in places where the pointer is not returned by the function (like raw pointers, when smart pointers are being used to do the memory management).

21stcn said...

I'm also stuck on that level of Spacechem.