Click here to Skip to main content
15,886,963 members
Articles / Programming Languages / C++

High Performance Dynamic Typing in C++ using a Replacement for boost::any

Rate me:
Please Sign up or sign in to vote.
4.77/5 (29 votes)
9 May 2011CPOL1 min read 229.8K   77   76
A high-performance alternative to boost::any.

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 not as efficient as it could be, so I have rewritten my own, which I have immodestly called cdiggins::any.

Updated in 2011: After five years, I have rewritten the class from scratch to fix some really nasty bugs and to simplify the code.

Using cdiggins::any

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

C++
any a = 42;
cout << a.cast<int>() << endl;
a = 13;
cout << a.cast<int>() << endl;
a = "hello";
cout << a.cast<const char*>() << endl;
a = std::string("1234567890");
cout << a.cast<std::string>() << endl;
int n = 42;
a = &n;
cout << *a.cast<int*>() << endl;
any b = true;
cout << b.cast<bool>() << endl;
swap(a, b);        
cout << a.cast<bool>() << endl;
a.cast<bool>() = false;
cout << a.cast<bool>() << endl;

Design of cdiggins::any

The cdiggins::any class contains two pointers: one to a policy class (any::policy) and one is a pointer to the data (any::data) which may in certain cases be used to contain the data itself (for example, if storing a primitive data type smaller than or equal to the size of a pointer).

The policy class is used for performing allocations, deallocation, copies, etc., and determines whether to use any::data to hold the data or point to the data.

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

C++
#pragma once
/*
 * (C) Copyright Christopher Diggins 2005-2011
 * (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
 */

#include <stdexcept>

namespace cdiggins
{
namespace anyimpl
{
    struct bad_any_cast 
    {
    };

    struct empty_any 
    {
    };

    struct base_any_policy 
    {
        virtual void static_delete(void** x) = 0;
        virtual void copy_from_value(void const* src, void** dest) = 0;
        virtual void clone(void* const* src, void** dest) = 0;
        virtual void move(void* const* src, void** dest) = 0;
        virtual void* get_value(void** src) = 0;
        virtual size_t get_size() = 0;
    };

    template<typename T>
    struct typed_base_any_policy : base_any_policy
    {
        virtual size_t get_size() { return sizeof(T); } 
    };

    template<typename T>
    struct small_any_policy : typed_base_any_policy<T>
    {
        virtual void static_delete(void** x) { }
        virtual void copy_from_value(void const* src, void** dest)
            { new(dest) T(*reinterpret_cast<T const*>(src)); }
        virtual void clone(void* const* src, void** dest) { *dest = *src; }
        virtual void move(void* const* src, void** dest) { *dest = *src; }
        virtual void* get_value(void** src) { return reinterpret_cast<void*>(src); }
    };

    template<typename T>
    struct big_any_policy : typed_base_any_policy<T>
    {
        virtual void static_delete(void** x) { if (*x) 
            delete(*reinterpret_cast<T**>(x)); *x = NULL; }
        virtual void copy_from_value(void const* src, void** dest) { 
           *dest = new T(*reinterpret_cast<T const*>(src)); }
        virtual void clone(void* const* src, void** dest) { 
           *dest = new T(**reinterpret_cast<T* const*>(src)); }
        virtual void move(void* const* src, void** dest) { 
          (*reinterpret_cast<T**>(dest))->~T(); 
          **reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src); }
        virtual void* get_value(void** src) { return *src; }
    };

    template<typename T>
    struct choose_policy 
    {
        typedef big_any_policy<T> type;
    };

    template<typename T> 
    struct choose_policy<T*> 
    { 
        typedef small_any_policy<T*> type; 
    };
    
    struct any;

    /// Choosing the policy for an any type is illegal, but should never happen.
    /// This is designed to throw a compiler error.
    template<>
    struct choose_policy<any>
    {
        typedef void type;
    };

    /// Specializations for small types.
    #define SMALL_POLICY(TYPE) template<> struct 
       choose_policy<TYPE> { typedef small_any_policy<TYPE> type; };

    SMALL_POLICY(signed char);
    SMALL_POLICY(unsigned char);
    SMALL_POLICY(signed short);
    SMALL_POLICY(unsigned short);
    SMALL_POLICY(signed int);
    SMALL_POLICY(unsigned int);
    SMALL_POLICY(signed long);
    SMALL_POLICY(unsigned long);
    SMALL_POLICY(float);
    SMALL_POLICY(bool);

    #undef SMALL_POLICY

    /// This function will return a different policy for each type. 
    template<typename T>
    base_any_policy* get_policy()
    {
        static typename choose_policy<T>::type policy;
        return &policy;
    };
}

struct any
{
private:
    // fields
    anyimpl::base_any_policy* policy;
    void* object;

public:
    /// Initializing constructor.
    template <typename T>
    any(const T& x) 
        : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) 
    {
        assign(x);
    }       

    /// Empty constructor. 
    any() 
        : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) 
    { }

    /// Special initializing constructor for string literals. 
    any(const char* x) 
        : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) 
    { 
        assign(x);
    }

    /// Copy constructor. 
    any(const any& x) 
        : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL)         
    {            
        assign(x);
    }

    /// Destructor. 
    ~any() {
        policy->static_delete(&object);
    }

    /// Assignment function from another any. 
    any& assign(const any& x) {
        reset();
        policy = x.policy;
        policy->clone(&x.object, &object);
        return *this;
    }

    /// Assignment function. 
    template <typename T>
    any& assign(const T& x) {
        reset();
        policy = anyimpl::get_policy<T>();
        policy->copy_from_value(&x, &object);
        return *this;
    }

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

    /// Assignment operator, specialed for literal strings. 
    /// They have types like const char [6] which don't work as expected. 
    any& operator=(const char* x) {
        return assign(x);
    }

    /// Utility functions
    any& swap(any& x) {
        std::swap(policy, x.policy);
        std::swap(object, x.object);
        return *this;
    }

    /// Cast operator. You can only cast to the original type.
    template<typename T>
    T& cast() {
        if (policy != anyimpl::get_policy<T>()) 
            throw anyimpl::bad_any_cast();
        T* r = reinterpret_cast<T*>(policy->get_value(&object)); 
        return *r;
    }

    /// Returns true if the any contains no value. 
    bool empty() const {
        return policy == anyimpl::get_policy<anyimpl::empty_any>();
    }

    /// Frees any allocated memory, and sets the value to NULL.
    void reset() {
        policy->static_delete(&object);
        policy = anyimpl::get_policy<anyimpl::empty_any>();
    }

    /// Returns true if the two types are the same. 
    bool compatible(const any& x) const {
        return policy == x.policy;
    }
};
}

Final Words

Thanks to Pablo Aguilar who helped with an early version of the cdiggins::any class. Thanks to Raute who helped me identify the critical problems in the original version of cdiggins::any.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Ara 3D
Canada Canada
I am the designer of the Plato programming language and I am the founder of Ara 3D. I can be reached via email at cdiggins@gmail.com

Comments and Discussions

 
QuestionCode dump of current, for copy & pasters: Pin
David O'Neil30-May-17 10:07
professionalDavid O'Neil30-May-17 10:07 
QuestionImplicit cast fix Pin
Member 1154432525-Mar-15 11:59
Member 1154432525-Mar-15 11:59 
QuestionLast version still buggy (but I don't have a solution) Pin
David O'Neil4-Oct-14 11:45
professionalDavid O'Neil4-Oct-14 11:45 
QuestionPlease someone add Move constructor Pin
jeefo124-May-14 16:54
jeefo124-May-14 16:54 
Questionnot working with "unsigned int" Pin
cristi.arghiroiu19-Feb-14 1:00
cristi.arghiroiu19-Feb-14 1:00 
Questionfix the const problem Pin
pj220_20068-Aug-13 22:12
pj220_20068-Aug-13 22:12 
if T has const prefix, some error will be occour. i also check the David's isA template , when T is a type with const prefix for example ( const char), a error is occour.because the variable temp must be initialized. in order to fix const problem, i add help template class ( remove_const ) to remove the const prefix to solve it. i also chenge David's IsA function to avoid using the temp variable.
C++
//-------------------------------------------------------------------------------------------
// The following is derived from Christopher Diggins, Pablo Aguilar, and Kevlin Henney's
// 'any' work available at:
// http://www.codeproject.com/Articles/11250/High-Performance-Dynamic-Typing-in-C-using-a-Repla.
//
// Their original copyright notice is as follows:
// * (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)
//
// The only three changes I have made is 1) to toss it into the 'DWL' namespace (as I
// don't like typing more than I have to, and those extra four letters add up, especially
// as I use it in function signatures), 2) replacement of the 'bad_any_cast' derived from
// std::bad_cast with a DwlException, and 3) added the templated 'isA' function to
// make testing for type much easier.  Of course, the seccond change makes 'catching'
// just this type of error much harder if you wish to do so, but you may change to your
// heart's content for your own projects since you have the code.
// Thanks Christopher, Pablo, and Kevlin for making this available.
//
// Oops - I forgot about the changes listed above in my preface to this code.
//-------------------------------------------------------------------------------------------

#ifndef RMW_CDIGGINS_ANY_HPP
#define RMW_CDIGGINS_ANY_HPP

//#pragma once
/*
 * (C) Copyright Christopher Diggins 2005-2011
 * (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
 */

#include <stdexcept>

namespace dwl 
{
	namespace anyimpl 
	{
		struct bad_any_cast 
		{
		};

		struct empty_any 
		{
		};

		struct base_any_policy
		{

			virtual void static_delete( void** x) = 0;

			virtual void copy_from_value(void const* src, void** dest) = 0;

			virtual void clone(void* const* src, void** dest) = 0;

			virtual void move(void* const* src, void** dest) = 0;

			virtual void* get_value(void** src) = 0;

			virtual size_t get_size() = 0;

		};

		template<typename T>
		struct typed_base_any_policy : base_any_policy 
		{
			virtual size_t get_size() { return sizeof(T); } 
		};

		template<typename T>
		struct small_any_policy : typed_base_any_policy<T> 
		{

			virtual void static_delete(void** x) { }

			virtual void copy_from_value(void const* src, void** dest) 
			{
				//This used to be:
				//new(dest) T(*reinterpret_cast<T const*>(src)); 
				//But this is the small_any_policy, and ints and other small objects
				//shouldn't need to be pointed to by an object.
				//In this case, we are really receiving the address of the the 'object' variable
				*dest = *(reinterpret_cast<void**>((const_cast<void*>(src))));
			}

			virtual void clone(void* const* src, void** dest) { *dest = *src; }

			virtual void move(void* const* src, void** dest) { *dest = *src; }

			virtual void* get_value(void** src) { return reinterpret_cast<void*>(src); }

		};

		template<typename T>
		struct remove_const 
		{
			typedef T type;
		};

		template<typename T> 
		struct remove_const<const T>
		{ 
			typedef T type; 
		};

		template<typename T>
		struct big_any_policy : typed_base_any_policy<T> 
		{
			virtual void static_delete( void** x) 
			{
				if (*x) 
				{
					delete (*reinterpret_cast< remove_const<T>::type**>(x));
				}
				*x = NULL;
			}

			virtual void copy_from_value(void const* src, void** dest) 
			{ 
				*dest = new remove_const<T>::type(*reinterpret_cast<T const*>(src));
			}

			virtual void clone(void* const* src, void** dest)
			{ 
				*dest = new remove_const<T>::type(**reinterpret_cast<T* const*>(src));
			}

			virtual void move(void* const* src, void** dest) 
			{ 
				(*reinterpret_cast<remove_const<T>::type**>(dest))->~T(); 
				**reinterpret_cast<remove_const<T>::type**>(dest) = **reinterpret_cast<T* const*>(src);
			}

			virtual void* get_value(void** src) { return *src; }
		};

		template<typename T>
		struct choose_policy 
		{
			typedef big_any_policy<T> type;
		};

		template<typename T> 
		struct choose_policy<T*>
		{ 
			typedef small_any_policy<T*> type; 
		};

		struct any; //Forward declaration

		/// Choosing the policy for an any type is illegal, but should never happen.
		/// This is designed to throw a compiler error.
		template<>
		struct choose_policy<any> 
		{
			typedef void type;
		};

		/// Specializations for small types.
#define SMALL_POLICY(TYPE) template<> struct choose_policy<TYPE> { typedef small_any_policy<TYPE> type; };
		SMALL_POLICY(signed char);
		SMALL_POLICY(unsigned char);
		SMALL_POLICY(signed short);
		SMALL_POLICY(unsigned short);
		SMALL_POLICY(signed int);
		SMALL_POLICY(unsigned int);
		SMALL_POLICY(signed long);
		SMALL_POLICY(unsigned long);
		SMALL_POLICY(float);
		SMALL_POLICY(bool);
		SMALL_POLICY(const signed char);
		SMALL_POLICY(const unsigned char);
		SMALL_POLICY(const signed short);
		SMALL_POLICY(const unsigned short);
		SMALL_POLICY(const signed int);
		SMALL_POLICY(const unsigned int);
		SMALL_POLICY(const signed long);
		SMALL_POLICY(const unsigned long);
		SMALL_POLICY(const float);
		SMALL_POLICY(const bool);
#undef SMALL_POLICY

		/// This function will return a different policy for each type. 
		template<typename T>
		base_any_policy* get_policy() 
		{
			static typename choose_policy<T>::type policy;
			return &policy;
		};

	}  //End of anyimpl namespace

	struct any 
	{
	private:
		// fields
		anyimpl::base_any_policy* policy;
		void* object;

	public:
		/// Initializing constructor.
		template <typename T>
		//This used to be:
		//any(const T& x) : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) {
		//I am changing it to force the initial construction to use the small_policy:
		any(const T& x) : policy(anyimpl::get_policy<unsigned int>()), object(NULL) 
		{
			assign(x);
		}      

		/// Empty constructor. 
		//This used to be:
		//any() : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) {
		//But why should an empty 'any' receive a default 'big_any_policy'???
		//This starts calling 'new' and 'delete' under the hood.
		//So let's try the following to force a small_policy:
		any() : policy(anyimpl::get_policy<unsigned int>()), object(NULL) 
		{
		}

		/// Special initializing constructor for string literals. 
		any(const char* x) : policy(anyimpl::get_policy<anyimpl::empty_any>()), 
			object(NULL) 
		{
				assign(x);
		}

		/// Copy constructor. 
		any(const any& x) : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) 
		{
			assign(x);
		}

		/// Destructor. 
		~any() 
		{
			policy->static_delete(&object);
		}

		/// Assignment function from another any. 
		any& assign(const any& x) 
		{
			reset();
			policy = x.policy;
			policy->clone(&x.object, &object);
			return *this;
		}

		/// Assignment function. 
		template <typename T>
		any& assign(const T& x)
		{
			reset();
			policy = anyimpl::get_policy<T>();
			policy->copy_from_value(&x, &object);
			return *this;
		}

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

		any& operator=(const any & x)
		{
			/*policy = x.policy;
			policy->copy_from_value(&x.object, &object);
			return *this;*/
			reset();
			return assign(x);
		}

		any& operator()(const any & x) 
		{
			policy = x.policy;
			policy->copy_from_value(&x.object, &object);
			return *this;
		}

		/// Assignment operator, specialed for literal strings. 
		/// They have types like const char [6] which don't work as expected. 
		any& operator=(const char* x) 
		{
			return assign(x);
		}

		/// Utility functions
		any& swap(any& x) 
		{
			std::swap(policy, x.policy);
			std::swap(object, x.object);
			return *this;
		}

		/// Cast operator. You can only cast to the original type.
		template<typename T>
		T& cast() 
		{
			if (policy != anyimpl::get_policy<T>()) 
			{
				throw anyimpl::bad_any_cast();
			}
			T* r = reinterpret_cast<T*>(policy->get_value(&object)); 
			return *r;
		}

		/// Returns true if the any contains no value. 
		bool empty() const 
		{
			return policy == anyimpl::get_policy<anyimpl::empty_any>();
		}

		/// Frees any allocated memory, and sets the value to NULL.
		void reset() 
		{
			policy->static_delete(&object);
			//The following used to be:
			//policy = anyimpl::get_policy<anyimpl::empty_any>();
			//But why should the policy default to the big_any_policy?  Therefore,:
			policy = anyimpl::get_policy<unsigned int>();
		}

		/// Returns true if the two types are the same. 
		bool isA(const any& x) const 
		{
			return policy == x.policy;
		}

		template<typename T>
		bool isA() 
		{
			return policy == anyimpl::get_policy<T>();
		}

	}; //End of 'any' struct definition
}

#endif


modified 9-Aug-13 4:51am.

AnswerRe: fix the const problem Pin
skyformat99@gmail.com31-Jan-14 21:47
skyformat99@gmail.com31-Jan-14 21:47 
AnswerRe: fix the const problem Pin
David O'Neil28-Feb-14 20:23
professionalDavid O'Neil28-Feb-14 20:23 
BugRe: fix the const problem Pin
Martial Spirit19-Mar-16 18:26
Martial Spirit19-Mar-16 18:26 
QuestionBugfix Pin
David O'Neil9-Jul-13 20:45
professionalDavid O'Neil9-Jul-13 20:45 
QuestionAn overlooked usage case in new code Pin
David O'Neil26-Jan-13 19:01
professionalDavid O'Neil26-Jan-13 19:01 
QuestionA small helper function for you Pin
David O'Neil24-Jan-13 17:53
professionalDavid O'Neil24-Jan-13 17:53 
Questionerror: 'TYPE' was not declared in this scope Pin
yurenjimi198520-Nov-12 15:44
yurenjimi198520-Nov-12 15:44 
AnswerRe: error: 'TYPE' was not declared in this scope Pin
Member 968039912-Dec-12 12:10
Member 968039912-Dec-12 12:10 
GeneralIt looks good, but is that dynamic typing? Pin
Jamming114-May-11 12:20
Jamming114-May-11 12:20 
GeneralRe: It looks good, but is that dynamic typing? Pin
MetaVoid29-Sep-11 9:46
MetaVoid29-Sep-11 9:46 
GeneralRe: It looks good, but is that dynamic typing? Pin
jcmalek30-Jan-15 5:49
jcmalek30-Jan-15 5:49 
GeneralMemory Leaks and Crashes when storing "non-primitiv" data! Pin
Raute11-Aug-10 3:57
Raute11-Aug-10 3:57 
GeneralRe: Memory Leaks and Crashes when storing "non-primitiv" data! Pin
Christopher Diggins9-May-11 15:31
professionalChristopher Diggins9-May-11 15:31 
GeneralQuestion on checking empty Pin
Member 421168429-Mar-10 9:26
Member 421168429-Mar-10 9:26 
GeneralPreventing Reallocations Pin
Anand Krishnamoorthi18-Apr-07 6:15
Anand Krishnamoorthi18-Apr-07 6:15 
QuestionSmall Types Assignment Problem? Pin
Anand Krishnamoorthi18-Apr-07 6:10
Anand Krishnamoorthi18-Apr-07 6:10 
AnswerRe: Small Types Assignment Problem? Pin
xryl6695-Sep-07 2:28
xryl6695-Sep-07 2:28 
GeneralCompile problem with cast Pin
Neville Franks17-Jul-06 13:15
Neville Franks17-Jul-06 13:15 
GeneralRe: Compile problem with cast Pin
pablo_mag17-Jul-06 14:00
pablo_mag17-Jul-06 14:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.