You are here:

C++/variable name and function name not known till run time

Advertisement


Question
Hello Ralph,
Is it possible with C++ to have my variables stored in a file , then at run time I initialize variables with those names,ex : in a file I will have a variable called book and besides it for example its value 5 "book 5"
then in the program I read this record and initialize the variable , also is it possible to have the function you want to call stored in another variable something like :
string funcToCall ="Addx";
funcToCall;

Thanks in advance

Answer
The short answer is no.

The longer answer is yes, but not directly. You have to provide the code to read the variable/function names and values and provide the code to map the name to the variable or function. This is because the names of functions and objects (variables) in your program are part of the program and the names of such things in your data file are data. When you compile and link a C++ program these names are thrown away. In fact some variables may be thrown away or not ever exist in main memory if the compiler (typically when optimising the generated machine code) determines it is safe to do so (i.e. doing so causes no observable difference to the meaning of the program). The same applies to functions. Specifically any function that is not used in your program is a candidate not to be included in the linked executable.

C++ has two mechanisms for handling runtime (or dynamic) requirements such as you mention. The first is dynamic object creation and destruction using the new and delete operators. This takes care of the case where you wish to create a variable as directed by some outside data source presented to the program at run time such as a file. The second is runtime function despatch, and is most readily supported by polymorphic object behaviour via class virtual (instance) member functions. If you are unfamiliar with these concepts then please read up on them in a good C++ reference. You could also read the relevant parts of the C++ FAQ at http://www.parashift.com/c++-faq-lite/.

When creating an object (variable) dynamically (at runtime) you need to specify the type of the object to be created. So unless this is invariant and is in effect a hard coded assumption made by the program, your file would need to provide some indication of the type of the variable to create. In your case your program might make a hard coded assumption that all variables (objects) created at runtime by reading the data in the file you talk about are of type int.

Another possibility is not to specify the type in the file but to assume that all data after the name of the variable up to the next variable name (or end of file) is just a string, and is stored as such. The data can be converted from a string to some other type quite easily. The obvious support for such conversions in C++ is to provide a stream extraction operator (operator>>) for the type and use std::istringstream to convert the string value to the required type value. Such a scheme would not be as type-safe as explicitly indicating the type required as there is no guarantee that the correct type for the variable is the only type to which such a conversion would work. For example a string of digits representing an integer value could just as well be converted to a double value.

I shall look at handling the creation of the correct type later on. First I would like to discuss how you can associate the name in the file with the value. For simplicity I shall assume the scheme mentioned above in which the data for the variable value is stored as a string.

The operative word in the above paragraph is 'associate'. This leads to the obvious thought that we should use an associative container to store variables read from your file. The type of associative container we would like would look up the variable using its name string and return its value (also a string in this example). That is we need to use key, value pairs, in which the variable name is the key and the variable value is the value (duh!). Such types are called maps or dictionaries. Now it sounds like quite a bit of work to produce such a map or dictionary type. This is where the standard C++ library comes to our rescue. It has just such a map type, in fact it is a type template. You include the header <map>, and use a specific specialisation of the std::map template class, specifying the key type and the value type. In this case both the key and the value are strings. Which leads to the next decision: what type of strings? I shall use the C++ std::string type. Here is an example type alias for our map type:

   #include <map>
   #include <string>

   // ...

   typedef std::map< std::string, std::string >  NamedStringValueMap;

Now we can use the type name NamedStringValueMap in place of std::map< std::string, std::string >.

We can insert new variable values into a map of type NamedStringValueMap, search for them by their (key) name, and retrieve their string value. For information about std::map, std::string and other such C++ standard library entities refer to a good C++ standard library book such as "The C++ Standard Library A Tutorial and Reference" by Nicolai M. Josuttis. Some online material can be found at http://www.sgi.com/tech/stl/ (although some things described here are non-standard extensions) and at http://www.digilife.be/quickreferences/PT.htm (std::map is part of the Standard Template Library or STL portion of the C++ standard library).

But how do we convert the variable string values to their required type? Well the basic idea is to convert the value to the type using a std::istringstream, for example to convert to an int value we might write something like:

   #include <sstream> // for std::istringstream

   // ...

   int value(0);
   std::string string_value( variableMap.Get("book") );
   std::istringstream inStrStrm(string_value);
   inStrStrm >> value;


In the above I assume we have the runtime loaded variables handled by an instance of an object called variableMap that is of a type that is a wrapper class that contains a NamedStringValueMap and provides useful operations for managing the runtime loaded variables. In this case I use a member function called Get that returns the string value for the variable whose name it is passed. Internally Get would handle the details of retrieving the value from the NamedStringValueMap.

Having retrieved the value of the variable as a string the value is converted to an int using a std::istringstream object.

Of course if we have provided the types for use with our string values with stream extraction operators (operator>>) to read (or get) values then one would expect that they also have stream insertion operators (operator<<) to write (or put or set) values. These of course can be used to update the string values stored in the NamedStringValueMap (via the interface provided by the variableMap type's wrapper of course), e.g.:

   value = 27;

   // ...

   std::ostringstream outStrStrm;
   outStrStrm << value;
   variableMap.Set("book", outStrStrm.str() );

Of course having to frequently write such chunks code becomes tedious. So just as I introduced the idea of a class to wrap the raw NamedStringValueMap, I suggest the raw value std::string be also wrapped in a class that adds member functions to perform such conversions. But, you say, doesn't this mean that we will need one get and one set for each type of value we wish to convert between string values? Yep. However, once again, C++ features come to the rescue, in this case in the form of member function templates. E.g.:

   class StringValue
   {
       std::string rep_

   public:
       // Get the StringValue's value as a type T
       // The type T must be usable with operator>>
       // @param value  [out] reference to object of type T which is
       //          to be set to the value represented by
       //          the StringValue instance.
       // @returns          true if successful; false otherwise.
       template < class T >
       bool GetValue( T & value ) const
       {
         std::istringstream inStrStrm(rep_);
         inStrStrm >> value;
         return inStrStrm.good();
       }

       // Set the StringValue's value from the value of a type T
       // The type T must be usable with operator<<
       // @param value  [in] reference to object of type T which is
       //          to be used to set to the value of the
       //          the StringValue instance.
       // @returns          true if successful; false otherwise.
       template < class T >
       bool SetValue( T const & value  )
       {
         std::ostringstream outStrStrm;
         outStrStrm << value;
         rep_ = outStrStrm.str();
         return outStrStrm.good();
       }
   };

We now use StringValue as the value type for the NamedStringValueMap value type:

   typedef std::map< std::string, StringValue >  NamedStringValueMap;

The template member functions define generic function templates which the compiler will use to produce the required GetValue and SetValue for the types converted to and from string values. If the associated wrapper class for the (updated) NamedStringValueMap type defines similar member function templates then we can write code like the following to perform the previous example get and set operations:

   int value(0);
   variableMap.Get("book", value );

   // ...

   value = 27;

   // ...

   variableMap.Set("book", value );

The Get and Set operations might throw an exception if they fail for some reason, the variable named does not exist or the conversion to or from a string value failed for example.

The class that wraps a NamedStringValueMap might define other operations: operations to save and restore the variables to and from file for example.

The above discourse has shown one simple way to implement a scheme that approximates what you wish to do with regard runtime variables. But what about specifying which function to call in a file? Well the way to go about this is to again think map. In this case the key is the function name as used in your file (a string as before) and the value will be something that represents the function to be called. These will have to be pre-defined (i.e. hard-coded) in your program so that the name, function representation pairs are inserted into the map during program startup.

There are problems with functions. First off functions having different signatures (i.e. different numbers and type of arguments and returned value) are not considered to be of the same type. Second, there are two main types of function in C++: non (instance) member functions and non member/static member functions. The latter type can have pointers to them created that can store a pointer to any function having a matching signature, and the pointed to function can be called through such a function pointer. Instance member functions however are different. You can create member function pointers but they cannot be used on their own, they require an instance (i.e. object) of the class to which they are a member to be useful.

Another option would be to only call functions concerned with an interface (i.e. polymorphically, using virtual member functions). The name of the function could then be used to create a specific sub-type of a base class that defines the virtual member functions, stored as a pointer to the base class. Retrieving this base pointer that really points to the derived sub-type object will allow the virtual function implementation of the derived sub-type to be called:

   class FunctionBase
   {
   public:
       virtual void Func() = 0;

       virtual ~FunctionBase() {}
   };

   class AddXFunction : public FunctionBase
   {
   public:
       virtual void Func();
   };

   void AddXFunction::Func()
   {
   // Do stuff...
   }

   // ...

   functionMap.Add("addx", new AddXFunction );

   // ...

   FunctionBase * pFn(0);
   functionMap.Get("addx", pFn);
   if (pFn)
   {
       pFn->Func(); // calls AddXFunction::Func
   }

You will have to decide what sort of functions and what range of signatures you require to be selectable from a file at runtime.

OK, so what if you decide to indicate in your file what sort of object you require created. Eventually you code would have to execute code such as the following:

   new Type;

i.e you have to map (again!) the value (string) indicating the type that was read from the file and arrange to use this to execute a new statement to create the required object type. One obvious method is to use a chained sequence to tests:

   int * pIntValue(0);
   double * pDblValue(0);

   // ...

   if ( typeString=="Integer" )
   {
       pIntValue = new int;
   }
   else if ( typeString=="Real")
   {
       pDblValue = new double;
   }  
   else ...

But this becomes tedious to maintain and is not very efficient - the more types are creatable dynamically the more tests may need to have to be performed before a match is located and a miss implies that all known values have been checked for.

The other problem with this scheme as it stands is that the types created are not related, so the pointers returned by new have to be stored in specific and separate pointer variables.

The classic response to this dilemma is to only allow creation of types that have a common base type (which precludes creating raw built-in types such as int and double - they would need class wrappers). The more generic the range of types you need to create dynamically the more generic (and usually less useful) are the operations this common base type can perform. To get back to the specific types you would need to perform a down cast using dynamic_cast:

   class BaseDynamicType
   {
   public:
       virtual ~BaseDynamicType() {}
   };
   class DynamicInteger : public BaseDynamicType
   {
   // ...
   };

   class DynamicReal : public BaseDynamicType
   {
   // ...
   };


   // ...

   BaseDynamicType * pDynamicObject(0);

   if ( typeString=="Integer" )
   {
       pDynamicObject = new DynamicInteger;
   }
   else if ( typeString=="Real")
   {
       pDynamicObject = new DynamicReal;
   }  
   else ...

So that solves the unrelated type problem, how about replacing the if ... else if ... else if ... if sequence? Well once again map can come to the rescue. The idea is to store items in the map that can create specific sub-types of BaseDynamicType (i.e. types like DynamicInteger and DynamicReal). There are various ways this can be done. One way is to define a companion set of types that are responsible for creating specific BaseDynamicType types:

   class BaseDynamicTypeFactory
   {
   public:
       virtual ~BaseDynamicTypeFactory() {}

       virtual BaseDynamicType * Create() = 0;
   };

   class DynamicIntegerFactory : public BaseDynamicTypeFactory
   {
   public:
       virtual BaseDynamicType * Create();
   };

   class DynamicRealFactory : public BaseDynamicTypeFactory
   {
   public:
       virtual BaseDynamicType * Create();
   };

   // ...


   BaseDynamicType  * DynamicIntegerFactory::Create()
   {
       return new DynamicInteger;
   }

   BaseDynamicType  * DynamicRealFactory::Create()
   {
       return new DynamicReal;
   }

   
We can then create and populate a map to create objects dynamically:

  typedef std::map<std::string, BaseDynamicTypeFactory*>  
         DynamicFactoryMap;

   // ...

   DynamicFactoryMap dynamicObjectCreator;
   dynamicObjectCreator.insert
         ( std::make_pair( "Integer"
         , new DynamicIntegerFactory
         )
         );
   dynamicObjectCreator.insert
         ( std::make_pair( "Real"
         , new DynamicRealFactory
         )
         );

Once the creator map holds an entry for creating a (dynamic) type it can be used to create instances of that type given the assoicated type name used as the key value:

   BaseDynamicType * pDynamicInteger
         ( dynamicObjectCreator["Integer"]->Create() );
   BaseDynamicType * pDynamicReal
         ( dynamicObjectCreator["Real"]->Create() );


In the case where you create the objects as directed from text read in from a file you would use the string containing the read type name in place of the hard coded names used in the example above:

   BaseDynamicType * pDynamicReal
         ( dynamicObjectCreator[typeString]->Create() );
  

I would suggest that you do _not_ use the above form to access entries in a map in production code in this case. If the map contains no entry with the specified key then it creates one having that key. In this case the value would be a null pointer - a bad state of affairs. Instead use the find operation of map, which returns either the position of the key, value pair of the entry or the end value if there is not entry in the map with the requested key. This can be used to write more robust code thus:


   DynamicFactoryMap::const_iterator
         pos = dynamicObjectCreator.find( typeString );
   if ( pos != mMap.end() )
   {
       value = pos->second;
   }

These are the basic ideas. You might like to look into several related libraries from the Boost library collection (see http://www.boost.org/):

Smart pointers, specifically boost::shared_pointer (see http://www.boost.org/libs/smart_ptr/smart_ptr.htm). These are useful when using pointers with C++ standard library containers such as std::map.

Boost any (see http://www.boost.org/doc/html/any.html) and variant (see http://www.boost.org/doc/html/variant.html) libraries. These are useful when handling heterogeneous types (like int and double) in the same container, and could potentially be used to get around using a common root base class for dynamic variable types.

Boost function (see http://www.boost.org/doc/html/function.html) and signals (see http://www.boost.org/doc/html/signals.html) libraries are useful when dealing with various types of functions and function objects (functors, types that have an operator()). The Boost bind library (see http://www.boost.org/libs/bind/bind.html) is also useful with these libraries.

You will need a decent up to date C++ compiler to use Boost libraries. The documentation for each library usually has information on which compilers it works with and how well.

There is a good introductory book to the Boost libraries called "Beyond the C++ Standard Library An Introduction to Boost" by Bjorn Karlsson.

Finally I remind you that the code shown here is for example and demonstration purposes only and is not in anyway intended to be of production quality.  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.