Channels ▼
RSS

C/C++

Twisting the RTTI System for Safe Dynamic Casts of void* in C++


Type Annotation through Exception Handling

More Insights

White Papers

More >>

Reports

More >>

Webcasts

More >>

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.


Related Reading






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video