Type Annotation through Exception Handling
More Insights
White Papers
- Unified Communications Buyer's Guide
- Real World Considerations for Implementing Desktop Virtualization eBook
Reports
More >>Webcasts
- Rising to the challenge of RMORSA - leveraging existing capabilities and building new ones
- Data Center Performance: Optimization Secrets Revealed
Despite its not so great performance, from the previous discussion try-catch seems to be the best trade off for any_ptr's type cast.
Still considering the hierarchy of previous section, we describe what happens when a bottom* is assigned to an any_ptr. Since any_ptr can be assigned to any pointer type, the constructor is a template. The received pointer value is saved into a member called, say, ptr_ of type void*.
When a client asks any_ptr for a pointer of type, say, middle*, ptr_ is cast to bottom* and the result is thrown to be caught by a catch(middle*) statement. The client no longer knows that the object is of type bottom. Hence, any_ptr must have saved this piece of information at construction time and, consequently, is responsible for doing the cast. It needs, then, a template function thrower depending on a parameter type T (bottom in this example) that casts ptr_ to T* and throws the result:
template <typename T>
void thrower(void* ptr) { throw static_cast<T*>(ptr); }
}
Two any_ptr objects that were assigned to different pointer types should call distinct instantiations of thrower. One might wish, then, for thrower to be virtual, but there is no such thing as a virtual template function. An alternative is having a member, say thr_, that is a pointer to function and stores the address of the correct instantiation of thrower.
Finally, any_ptr implements a method to do the try-catch cast. Here is a draft of any_ptr:
class any_ptr {
void* ptr_;
void (*thr_)(void*);
template <typename T>
static void thrower(void* ptr) { throw static_cast<T*>(ptr); }
public:
template <typename T>
any_ptr(T* ptr) : ptr_(ptr), thr_(&thrower<T>) {}
template <typename U>
U* cast() const {
try { thr_(ptr_); }
catch (U* ptr) { return static_cast<U*>(ptr); }
catch (...) {}
return 0;
}
};
Improving any_ptr
In the draft implementation, thrower is a static member function that receives the pointer to be cast. It makes more sense declaring thrower non static and making it throw ptr_ instead of an "outside" pointer. This wasn't done above to simplify thr_'s declaration, avoiding the cumbersome syntax of pointer to member function.
To reduce the risk of a memory leak, we replace void* with boost::shared_ptr<void> [1], but there's a price to pay. Using smart pointers prevents the compiler to see inheritance as clearly as it does when using raw pointers. For example, since bottom inherits from top, in many circumstances the compiler sees bottom* also as a top*. In particular, a thrown bottom* can be caught as a top*. However, the compiler never sees a boost::shared_ptr<bottom> as a boost::shared_ptr<top>. Therefore, thrower must use raw pointers. This is easy because boost::shared_ptr implements the method get() to read the inner raw pointer:
template <typename T>
void thrower() const {
throw static_cast<T*>(ptr_.get());
}
The corresponding change in the catch statement seems obvious:
catch (U* p) {
::boost::shared_pointer<U*> result = ::boost::static_pointer_cast<U>(ptr_);
return result;
}
Unfortunately, this isn't quite correct. The problem pops up under multiple inheritance because casting a pointer might change the address. More precisely, consider the cast U* p = static_cast<U*>(q). Then, q and p don't have the same value necessarily; that is, they don't point to the same memory location. Referring to the attempt above, it means that p and result.get() might point to different addresses. Therefore, result is invalid and dereferencing it has undefined behavior.
To fix this, we need to assign result's internal raw pointer, px, to p but, normally, px is a private member and boost::shared_ptr doesn't implement any method that changes px without touching the reference counter.
An non satisfactory work-around is defining the macro BOOST_NO_MEMBER_TEMPLATE_FRIENDS before including boost/shared_ptr.hpp. This makes px to be public. But it's not enough to define this macro in any_ptr.h because clients may include boost/shared_ptr.hpp before any_ptr.h. Therefore, they must define the macro. This is a less serious constraint than requiring clients to make their classes polymorphic. But this is something that we don't want to enforce.
There are a few ways to get our hands on the private members of boost::shared_ptr. Most of them are illegal. One exception is the lawyer's approach as described by Herb Sutter [4]. As he says, although legal, this is a very arguable technique. It would be better if any_ptr were a friend class of boost::shared_ptr but, apart from patching boost/shared_ptr.hpp, this is beyond our scope.
Other improvements include implementing:
- Explicit constructors taking raw pointers and
boost::shared_ptr's; - Automatic conversion to
boost::shared_ptr(some would prefer explicit methods as in previous draft); - Non throwing swaps (as a member and as a specialization of
std::swap); - Automatic conversion to
bool.
An implementation of any_ptr is given in any_ptr.h.
Acknowledgments
The author would like to thank Lorenz Schneider and Victor Bogado for their comments and careful reading of this article.
References
[1] Boost, http://www.boost.org.
[2] KeyValue Library, http://keyvalue.sourceforge.net.
[3] Cleeland, C., Schmidt, D. C. and Harrison, T. H., "External Polymorphism," Proceedings of the 3rd Pattern Languages of Programming Conference, Allerton Park, Illinois, September 4-6, 1996.
[4] Sutter, H. Exceptional C++ Style, Addison-Wesley, 2000.
Cassio Neri has a PhD in Mathematics. He works in the FX Quantitative Research at Lloyds Banking Group in London. He can be contacted at cassio.neri@gmail.com.


