This post originated from an RSS feed registered with Java Buzz
by noe casas.
Original Post: operator->() ad infinitum
Feed Title: SoftwareDesignStuff
Feed URL: http://feeds2.feedburner.com/softwaredesignstuff
Feed Description: This blog is a place to share my ideas and opinions on topics like software design, good/bad programming practices, design patterns, programming languages, frameworks, software technology and so on, and to discuss them with you.
In C++, if an implementation of operator->() returns something different than a raw pointer, operator->() will be invoked on that return value; this will happen again and again until at some point a raw pointer is returned.
Lil' longer version:
One of the operators C++ allows to be overloaded is "->" . This enables syntactic constructions that mimic pointer usage, like smart pointers, which look like this...
Thus, you can use MySmartPointer with the same syntax as a plain T* :
MyClass* x (new MyClass); x->doStuff();
MySmartPointer <MyClass> y (new MyClass); y->doStuff();
However, if we declare operator->() to return something that is not a raw pointer, the compiler will generate code to invoke operator-> on that object, and so on:
void doStuff () { Foo foo; Bar bar (foo.operator->()); Baz* baz = bar.operator->(); baz->sayHello(); }
Bjarne Stroustrup used this language feature -plus a stack allocated auxiliary object- in a proposal for a C++ idiom for wrapping calls to member functions, enabling to perform stuff just before and just after any method call (i.e. intercept it). Let's see it in an example:
template<typename T> class LockSmartPtr { public: LockSmartPtr(T* t) : t_(t) {} LockSmartPtrAux<T> operator->() { return LockSmartPtrAux<T>(t_, lock_); } private: T* t_; Lock lock_; }; // LockSmartPtrAux should be declared inside LockSmartPtr to enhance symbol locality
With the class templates above, you can "wrap" any class so that every invocation to its member functions is guarded by a lock, thus achieving some degree of transparent thread safety:
map<string, MyClass> cache; /* fill cache with useful and hard to build stuff... */ LockSmartPtr<map<string,MyClass> > threadsafeCache(&cache); doSomethingInAnotherThread1 (threadsafeCache); doSomethingInAnotherThread2 (threadsafeCache);
Here follow some more details about the implementation...
To just perform some action (e.g. lock_.lock() ) BEFORE the "intercepted" call, you don't need an extra auxiliary object (e.g. LockSmartPtrAux), but only to invoke it before returning the pointee.
However, to perform some action (e.g. lock_.unlock() ) just AFTER the intercepted call and before the return, you have to place that action in the destructor of an auxiliary object and use operator-> trick.
That's all the C++ trickery for today, hope you liked it.
NOTE: my implementation of LockSmartPtr assumes your compiler implements the name return value optimization (NRVO); if this does not hold true and you use non recursive locks, you will probably get a nasty deadlock due to temporary copies of LockSmartPtrAux locking an already locked mutex from the same thread. Check out Stroutstrup's paper to see how (managing lock ownership, forbidding assignment) he tackles this and other issues.