5,424,116 members and growing! (18,782 online)
Email Password   helpLost your password?
Languages » C / C++ Language » General     Intermediate

High Performance Dynamic Typing

By Christopher Diggins

A high-performance alternative to boost::any.
C++, Windows, Visual Studio, Dev

Posted: 8 Aug 2005
Updated: 8 Aug 2005
Views: 46,588
Bookmarked: 31 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
19 votes for this Article.
Popularity: 5.96 Rating: 4.66 out of 5
1 vote, 5.3%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
2 votes, 10.5%
4
16 votes, 84.2%
5

Introduction

The Boost library provides a very useful little class called boost::any which can contain a value of virtually any type as long as that value supports copy construction and assignment. This boost::any type allows dynamic querying of the contained type, and safe type conversions. Despite its usefulness, boost::any is nowhere near as efficient as it could be, so I have rewritten my own, which I have immodestly called cdiggins::any.

Using cdiggins::any

The cdiggins::any type can be used to hold normal value types, and provides a mechanism to safely explicitly cast back to the appropriate type.

  cdiggins::any a = 42;
  int n = a.cast<int>();

If you try to cast to the wrong type an exception will be thrown:

  cdiggins::any a = 3.14;
  try {
    int n = a.cast<int>();
    cout << "should never be reached" << endl;
  }
  except(...) {
    cout << "just what I expected, an exception" << endl;
  }

You can use the cdiggins::any type to hold your own types if they support assignment and copy construction:

  struct fubar {
    int fu;
    int bar;
  };

  a = fubar();

You can extract type information at run-time using the type() member function. This returns a type_info struct describing the type of value being held:

  cout << a.type().name() << endl

Performance of cdiggins::any versus boost::any

The major drawback of the boost::any type is that it is quite slow because it indiscriminately uses dynamic allocations. The other drawback is that for every value type held there is the requirement for two extra pointers, one for a virtual function table and another for a pointer to the value. The cdiggins::any exhibits a very significant performance benefit (up to 40x in some cases) over boost::any.

There are several optimizations I used in the development of cdiggins::any in order to achieve these results:

  • there is only one extra pointer needed per instantiation of an any type.
  • types which have a size equal to or less than a void* do not undergo dynamic allocations when assigning a new value to an any which has the same underlying type, no dynamic reallocation occurs.

The Implementation

Finally without further ado, here is the implementation of the cdiggins::any class for your enjoyment:

/*
 * (C) Copyright Christopher Diggins 2005
 * (C) Copyright Pablo Aguilar 2005
 * (C) Copyright Kevlin Henney 2001
 *
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef CDIGGINS_ANY_HPP
#define CDIGGINS_ANY_HPP

#include <stdexcept>

#include <typeinfo>

#include <algorithm>


namespace cdiggins
{
  struct bad_any_cast : std::bad_cast {
    bad_any_cast(const std::type_info& src, const std::type_info& dest)
      : from(src.name()), to(dest.name())
    { }
    virtual const char* what() {
      return "bad cast";
    }
    const char* from;
    const char* to;
  };

  namespace any_detail {
    // function pointer table

    struct fxn_ptr_table {
      const std::type_info& (*get_type)();
      void (*static_delete)(void**);
      void (*clone)(void* const*, void**);
      void (*move)(void* const*,void**);
    };

    // static functions for small value-types

    template<bool is_small>
    struct fxns
    {
      template<typename T>
      struct type {
        static const std::type_info& get_type() {
          return typeid(T);
        }
        static void static_delete(void** x) {
          reinterpret_cast<T*>(x)->~T();
        }
        static void clone(void* const* src, void** dest) {
          new(dest) T(*reinterpret_cast<T const*>(src));
        }
        static void move(void* const* src, void** dest) {
          reinterpret_cast<T*>(dest)->~T();
          *reinterpret_cast<T*>(dest) = *reinterpret_cast<T const*>(src);
         }
      };
    };

    // static functions for big value-types (bigger than a void*)

    template<>
    struct fxns<false>
    {
      template<typename T>
      struct type {
        static const std::type_info& get_type() {
          return typeid(T);
        }
        static void static_delete(void** x) {
          delete(*reinterpret_cast<T**>(x));
        }
        static void clone(void* const* src, void** dest) {
          *dest = new T(**reinterpret_cast<T* const*>(src));
        }
        static void move(void* const* src, void** dest) {
          (*reinterpret_cast<T**>(dest))->~T();
          **reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src);
        }
      };
    };

    template<typename T>
    struct get_table
    {
      static const bool is_small = sizeof(T) <= sizeof(void*);

      static fxn_ptr_table* get()
      {
        static fxn_ptr_table static_table = {
          fxns<is_small>::template type<T>::get_type
        , fxns<is_small>::template type<T>::static_delete
        , fxns<is_small>::template type<T>::clone
        , fxns<is_small>::template type<T>::move
        };
        return &static_table;
      }
    };

    struct empty {
    };
  } // namespace any_detail


  struct any
  {
    // structors

    template <typename T>
    any(const T& x) {
      table = any_detail::get_table<T>::get();
      if (sizeof(T) <= sizeof(void*)) {
        new(&object) T(x);
      }
      else {
        object = new T(x);
      }
    }

    any() {
      table = any_detail::get_table<any_detail::empty>::get();
      object = NULL;
    }

    any(const any& x) {
      table = any_detail::get_table<any_detail::empty>::get();
      assign(x);
    }

    ~any() {
      table->static_delete(&object);
    }

    // assignment

    any& assign(const any& x) {
      // are we copying between the same type?

      if (table == x.table) {
        // if so, we can avoid reallocation

        table->move(&x.object, &object);
      }
      else {
        reset();
        x.table->clone(&x.object, &object);
        table = x.table;
      }
      return *this;
    }

    template <typename T>
    any& assign(const T& x)
    {
      // are we copying between the same type?

      any_detail::fxn_ptr_table* x_table = any_detail::get_table<T>::get();
      if (table == x_table) {
        // if so, we can avoid deallocating and resuse memory

        if (sizeof(T) <= sizeof(void*)) {
          // create copy on-top of object pointer itself

          new(&object) T(x);
        }
        else {
          // create copy on-top of old version

          new(object) T(x);
        }
      }
      else {
        reset();
        if (sizeof(T) <= sizeof(void*)) {
          // create copy on-top of object pointer itself

          new(&object) T(x);
          // update table pointer

          table = x_table;
        }
        else {
          object = new T(x);
          table = x_table;
        }
      }
      return *this;
    }

    // assignment operator

    template<typename T>
    any& operator=(T const& x) {
      return assign(x);
    }

    // utility functions

    any& swap(any& x) {
      std::swap(table, x.table);
      std::swap(object, x.object);
    }

    const std::type_info& get_type() const {
      return table->get_type();
    }

    template<typename T>
    const T& cast() const {
      if (get_type() != typeid(T)) {
        throw bad_any_cast(get_type(), typeid(T));
      }
      if (sizeof(T) <= sizeof(void*)) {
        return *reinterpret_cast<T const*>(&object);
      }
      else {
        return *reinterpret_cast<T const*>(object);
      }
    }

  // implicit casting is disabled by default 

  #ifdef ANY_IMPLICIT_CASTING
    // automatic casting operator

    template<typename T>
    operator T() const {
      return cast<T>();
    }
  #endif // implicit casting


    bool empty() const {
      return table == any_detail::get_table<any_detail::empty>::get();
    }

    void reset()
    {
      if (empty()) return;
      table->static_delete(&object);
      table = any_detail::get_table<any_detail::empty>::get();
      object = NULL;
    }

    // fields

    any_detail::fxn_ptr_table* table;
    void* object;
  };

  // boost::any-like casting

  template<typename T>
  T* any_cast(any* this_) {
    if (this_->get_type() != typeid(T)) {
      throw bad_any_cast(this_->get_type(), typeid(T));
    }
    if (sizeof(T) <= sizeof(void*)) {
      return reinterpret_cast<T*>(&this_->object);
    }
    else {
      return reinterpret_cast<T*>(this_->object);
    }
  }

  template<typename T>
  T const* any_cast(any const* this_) {
    return any_cast<T>(const_cast<any*>(this_));
  }

  template<typename T>
  T const& any_cast(any const& this_){
    return *any_cast<T>(const_cast<any*>(&this_));
  }
}

#endif // CDIGGINS_ANY_HPP

Under the Hood

This is a pretty wild class, but the idea is quite simple. A cdiggins::any instance contains two pointers: one points to a static table of function pointers, while the other points to a copy of the value. But here is the tricky trick I used to get fast performance for small types: if the value being held can fit inside of a void*, I don't actually bother allocating a new object, I force it into the pointer itself using placement new.

The only other somewhat tricky thing I do is create a set of static functions at compile-time for the type being contained. This is done through a call to the static template function get_table(). Using template specialization techniques, I am able to generate a set of function calls based on whether or not the type passed fits inside of a void pointer or not.

Final Words

Special thanks to Pablo Aguilar who pushed the code to new limits of safety and compiler compatibility. Pablo will be hopefully actively making more changes and making an official submission of the type to Boost. Best of luck Pablo!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Christopher Diggins


  I've been programming personal computers since my I got my first Atari 400 in 1980. I am currently self-employed as a consultant and author.

  I've worked as a professional programmer for over 12 years. I have written for Doctor Dobbs Journal, and C++ Users Journal. I also contributed to the C++ Cookbook released by O'Reilly.

  My area of research is the design and implementation of programming languages. My current project is the Cat programming language which is a cross between Forth and Haskell.
Occupation: Web Developer
Location: Canada Canada

Other popular C / C++ Language articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 25 of 56 (Total in Forum: 56) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralPreventing ReallocationsmemberAnand Krishnamoorthi7:15 18 Apr '07  
QuestionSmall Types Assignment Problem?memberAnand Krishnamoorthi7:10 18 Apr '07  
AnswerRe: Small Types Assignment Problem?memberxryl6693:28 5 Sep '07  
GeneralCompile problem with castmemberNeville Franks14:15 17 Jul '06  
GeneralRe: Compile problem with castmemberpablo_mag15:00 17 Jul '06  
GeneralRe: Compile problem with cast [modified]memberNeville Franks15:31 17 Jul '06  
GeneralRe: Compile problem with cast [modified]memberpablo_mag16:55 17 Jul '06  
GeneralRe: Compile problem with castmemberNeville Franks19:53 17 Jul '06  
GeneralCompile error with const char*memberfarshizzo8:23 26 Apr '06  
GeneralRe: Compile error with const char*memberpablo_mag14:47 17 Jul '06  
GeneralUpdate Postedmemberpablo_mag23:06 18 Sep '05  
GeneralRe: Update PostedmemberChristopher Diggins4:58 19 Sep '05  
GeneralRe: Update PostedmemberDavid O'Neil8:31 21 Sep '05  
GeneralRe: Update Postedmemberpablo_mag8:33 21 Sep '05  
GeneralRe: Update PostedmemberTemp837:03 16 Feb '06  
GeneralRe: Update Postedmemberpablo_mag7:18 16 Feb '06  
GeneralRe: Update PostedmemberTemp833:34 17 Feb '06  
GeneralRe: Update Postedmemberpablo_mag12:36 17 Feb '06  
GeneralRe: Update PostedmemberTemp837:26 18 Feb '06  
GeneralRe: Update Postedmemberpablo_mag11:31 18 Feb '06  
GeneralRe: Update PostedmemberTemp838:07 21 Feb '06  
GeneralRe: Update Postedmemberpablo_mag8:14 21 Feb '06  
GeneralTrouble with classes with 'any' members used in vectorsmemberDavid O'Neil8:42 12 Sep '05  
GeneralRe: Trouble with classes with 'any' members used in vectorsmemberpablo_mag13:05 16 Sep '05