Channels ▼
RSS

Templates for Efficient Dynamic Type Checking


November 1999/Templates for Efficient Dynamic Type Checking

Here's a handy template that makes it easy to check your casts without losing performance.


Casting is unavoidable with today's application frameworks. It is also a known source of bugs. To help make casting safer, I have written assert_cast, a set of C++ function templates. These functions perform dynamic type checking in debug mode, without introducing any overhead in the release build, and without the extra clutter of macros like MFC's ASSERT_KINDOF. A sample usage with MFC is as follows:

CMyView* pView = assert_cast<CMyView*>(GetActiveView());

The above statement verifies that the returned "active view" is really a kind of CMyView.

I've implemented assert_cast as a pair of overloaded function templates (see Listing 1, cast.h). The compiler deduces the source type from the function argument and chooses one of the two overloads. The compiler cannot deduce the result type from the argument, so you must specify that explicitly (as in the MFC example above).

The overloading is needed because dynamic_cast has different semantics for pointers and references. For pointers, a failed dynamic_cast returns a null pointer, while for references, it throws std::bad_cast.

In the pointer case, assert_cast wraps the dynamic_cast with the assert macro. When NDEBUG is defined, the macro expands to nothing, the dynamic_cast is never evaluated, and the entire function can be inlined to a simple static_cast. On the other hand, when NDEBUG is not defined, the validity of the cast is checked, using RTTI (Run-Time Type Identification) when needed. If the cast is invalid, the equality test fails and the program aborts with a diagnostic [2].

The reference case is similar, but in this case the code catches exceptions explicitly and converts them to assertions. In both cases, the results of dynamic_cast and static_cast are compared to trap the occasional perverse situation in which dynamic_cast succeeds but static_cast produces a different and incorrect result [3]. If an assertion trips, you can then go in with the debugger, examine the call stack at the moment of termination, and locate the offending cast.

Note that I've used NDEBUG and assert, rather than _DEBUG and ASSERT. The latter are not part of the C++ Standard, and there's no need for them here. Many development environments (including MSVC) will automatically define NDEBUG when appropriate, but check yours before you rely on it. NDEBUG must be consistently defined (or not defined) for all the files in a program; otherwise, violations of the ODR (One Definition Rule) [4] may result.

I designed assert_cast to use assertions instead of exceptions for several reasons. First, I wanted it to be usable even in environments where exceptions are considered unacceptable. Second, I wanted it to have no run-time overhead; this precludes checking in the release build. If exceptions were thrown only in the debug build, a program might inadvertently come to depend on handling them, only to fail when the checking is removed. Finally, any program behavior that differs between debug and release builds can be a source of insidious bugs that elude detection during testing, emerge in the field, then mysteriously vanish again when brought home to the debugger. Assertions can't be "handled," so they avoid this problem.

Workarounds

The templates in Listing 1 look innocuous enough; unfortunately, in practice they can put a strain on a compiler's template argument deduction and overload resolution capabilities. Of the three C++ compilers I tried (Intel 4.0, gcc 2.95, MSVC 6), only the Intel could digest my test module. For the others, various hacks are necessary.

For gcc, my workaround was to use a class template that feels like a function template. Then I used partial specialization on the result type to distinguish reference casts from pointer casts. (Function templates do not support partial specialization, although a similar technique, partial ordering, can often be used to get the same effect.) With the workaround, the MFC example above would actually construct a temporary object of type assert_cast<CMyView *>, then automatically invoke a conversion operator to return a value of type CMyView *, which would be assigned to pView. The code to implement this is not particularly readable, but normally you don't need to be concerned with the internal details of the template. In practice it's quite efficient and easy to use. The code is shown in Listing 2, alt_cast.h.

I declare (but do not define) the general version of assert_cast, so the compiler will understand the specializations that follow. Then I define a specialization for pointer casts. The constructor for assert_cast is itself a template function, which converts the source to the result type via static_cast. (At this point, the compiler will reject any casts that don't qualify for static_cast, such as conversions between unrelated types.) The result of the conversion is stashed away in m_pResult.

The body of the constructor handles the actual dynamic type checking, using essentially the same assertion as the code in Listing 1. If the cast is valid, the constructor returns and operator Result* is automatically invoked, returning the pointer stashed in m_pResult.

The specialization for references works similarly. The reference is converted to a pointer for dynamic_cast, since pointer casts have more convenient semantics.

The workaround in Listing 2 operates transparently on both pointers and references. Unfortunately, it must rely on template partial specialization, which some compilers (notably MSVC) do not support. For those compilers, an alternative workaround is to rename the function template for references (from Listing 1) to, say, assert_ref_cast. Since there's no overloading, there are no ambiguities, but the user must remember two different function names, which is a little inconvenient.

If you're not sure if your compiler needs a workaround, you can download the file democast.cpp from the CUJ ftp site (see p. 3 for downloading instructions) and try compiling it. If you get no errors, you should be able to use the code from Listing 1. If your compiler chokes, download alt_cast.h, uncomment the line #include "alt_cast.h" in democast.cpp, and try again. If your compiler still chokes, you'll have to rename one of the templates in cast.h.

Usage

So, when should assert_cast be used? First, you might consider whether your program can be designed to avoid casting. Dynamic type checking is "safe" in the sense that it does catch type errors, but the program must first be running. Static typing, on the other hand, catches errors at compile time. This is faster and more reliable. Templates can often eliminate the need for casting, or at least hide it behind a safe interface. (You won't find a single dynamic_cast in the entire STL.)

When casting can't be avoided, assert_cast becomes an option. For class hierarchy navigation, assert_cast is more robust than either static_cast or the C-style cast. Given a polymorphic type, assert_cast can do downcasts as well as the occasional upcast or cast to a sibling type.

assert_cast does have some limitations. It won't let you accidentally or deliberately cast away constness. Unlike plain dynamic_cast, it cannot do cross-casts and it cannot cast from a virtual base.

The most common use for assert_cast is in working with frameworks. These frameworks offer services in terms of the frameworks' own types, but users need to operate on their own derived types. To make the conversion, a downcast is required. The MFC code above is an example of this usage. In such cases, dynamic_cast is also an option, but assert_cast will produce smaller, faster code. I have found that since I don't need to worry about run-time overhead with assert_cast, I use it more regularly than I would have used dynamic_cast, and as a result my programs are more thoroughly checked.

References

[1] To be more precise, the example assumes that CMyView is derived from CView, a polymorphic type. GetActiveView returns a CView *. If the returned pointer actually references an object of type CMyView or any type derived from CMyView, the cast will succeed and a valid CMyView * will be returned. On the other hand, if the object is not a kind of CMyView, an assertion will be raised.

[2] The Standard specifies a call to std::abort, but some implementations will trigger a debug break instead.

[3] For an example, see the diamond-shaped hierarchy described by Bjarne Stroustrup in The Design and Evolution of C++ (Addison-Wesley, 1994), section 14.3.2.1.

[4] The One-Definition Rule requires that, in a given program, all definitions of a non-local name must be identical. Since the assert macro expands differently depending on whether NDEBUG is defined, the ODR is easily violated if assert is used in inline functions (such as the assert_cast constructor) and NDEBUG is not consistently defined.

Ivan J. Johnson is a software engineer and consultant in Sacramento, CA. He can be reached at ijohnson@alum.mit.edu.


Related Reading


More Insights






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