- •Introduction
- •Saving Time with This Book
- •Conventions Used in This Book
- •Part II: Working with the Pre-Processor
- •Part III: Types
- •Part IV: Classes
- •Part V: Arrays and Templates
- •Part VI: Input and Output
- •Part VII: Using the Built-in Functionality
- •Part VIII: Utilities
- •Part IX: Debugging C++ Applications
- •Part X: The Scary (or Fun!) Stuff
- •Icons Used in This Book
- •Creating and Implementing an Encapsulated Class
- •Creating a Mailing-List Application
- •Testing the Mailing-List Application
- •Customizing a Class with Polymorphism
- •Testing the Virtual Function Code
- •Why Do the Destructors Work?
- •Delayed Construction
- •The cDate Class
- •Testing the cDate Class
- •Creating the Header File
- •Testing the Header File
- •The Assert Problem
- •Fixing the Assert Problem
- •Using the const Construct
- •Identifying the Errors
- •Fixing the Errors
- •Fixing What Went Wrong with the Macro
- •Using Macros Appropriately
- •Using the sizeof Function
- •Evaluating the Results
- •Using sizeof with Pointers
- •Implementing the Range Class
- •Testing the Range Class
- •Creating the Matrix Class
- •Matrix Operations
- •Multiplying a Matrix by a Scalar Value
- •Multiplying a Matrix by Scalar Values, Take 2
- •Testing the Matrix Class
- •Implementing the Enumeration Class
- •Testing the Enumeration Class
- •Implementing Structures
- •Interpreting the Output
- •Defining Constants
- •Testing the Constant Application
- •Using the const Keyword
- •Illustrating Scope
- •Interpreting the Output
- •Using Casts
- •Addressing the Compiler Problems
- •Testing the Changes
- •Implementing Member-Function Pointers
- •Updating Your Code with Member-Function Pointers
- •Testing the Member Pointer Code
- •Customizing Functions We Wrote Ourselves
- •Testing the Default Code
- •Fixing the Problem
- •Testing the Complete Class
- •Implementing Virtual Inheritance
- •Correcting the Code
- •Rules for Creating Overloaded Operators
- •Using Conversion Operators
- •Using Overloaded Operators
- •Testing the MyString Class
- •Rules for Implementing new and delete Handlers
- •Overloading new and delete Handlers
- •Testing the Memory Allocation Tracker
- •Implementing Properties
- •Testing the Property Class
- •Implementing Data Validation with Classes
- •Testing Your SSN Validator Class
- •Creating the Date Class
- •Testing the Date Class
- •Some Final Thoughts on the Date Class
- •Creating a Factory Class
- •Testing the Factory
- •Enhancing the Manager Class
- •Implementing Mix-In Classes
- •Testing the Template Classes
- •Implementing Function Templates
- •Creating Method Templates
- •Using the Vector Class
- •Creating the String Array Class
- •Working with Vector Algorithms
- •Creating an Array of Heterogeneous Objects
- •Creating the Column Class
- •Creating the Row Class
- •Creating the Spreadsheet Class
- •Testing Your Spreadsheet
- •Working with Streams
- •Testing the File-Reading Code
- •Creating the Test File
- •Reading Delimited Files
- •Testing the Code
- •Creating the XML Writer
- •Testing the XML Writer
- •Creating the Configuration-File Class
- •Setting Up Your Test File
- •Building the Language Files
- •Creating an Input Text File
- •Reading the International File
- •Testing the String Reader
- •Creating a Translator Class
- •Testing the Translator Class
- •Creating a Virtual File Class
- •Testing the Virtual File Class
- •Using the auto_ptr Class
- •Creating a Memory Safe Buffer Class
- •Throwing and Logging Exceptions
- •Dealing with Unhandled Exceptions
- •Re-throwing Exceptions
- •Creating the Wildcard Matching Class
- •Testing the Wildcard Matching Class
- •Creating the URL Codec Class
- •Testing the URL Codec Class
- •Testing the Rot13 Algorithm
- •Testing the XOR Algorithm
- •Implementing the transform Function to Convert Strings
- •Testing the String Conversions
- •Implementing the Serialization Interface
- •Creating the Buffer Class
- •Testing the Buffer Class
- •Creating the Multiple-Search-Path Class
- •Testing the Multiple-Search-Path Class
- •Testing the Flow Trace System
- •The assert Macro
- •Logging
- •Testing the Logger Class
- •Design by Contract
- •Adding Logging to the Application
- •Making Functions Inline
- •Avoiding Temporary Objects
- •Passing Objects by Reference
- •Choosing Initialization Instead of Assignment
- •Learning How Code Operates
- •Testing the Properties Class
- •Creating the Locking Mechanism
- •Testing the Locking Mechanism
- •Testing the File-Guardian Class
- •Implementing the Complex Class
- •Creating the Conversion Code
- •Testing the Conversion Code
- •A Sample Program
- •Componentizing
- •Restructuring
- •Specialization
- •Index
Creating the Configuration-File Class |
251 |
The capability to configure applications is the hallmark of a professional program. If you build in the configuration options from the start of the design (rather than hacking on some configurations at the end of the process), the result is a much more robust and extensible application. Even if you add new options later on, the basis for the code will already be there.
Creating the Configuration-File
Class
The configuration-file class encapsulates the reading, parsing, and storing of the data in the text-based configuration file. The following steps show you how to build a stand-alone class that can simply be moved from application to application, allowing you to save time and have a consistent interface.
1. In the code editor of your choice, create a new file to hold the definition for your configuration-file class.
In this example, the file is named
ConfigurationFile.h, although you can use whatever you choose.
2. Type the code from Listing 45-1 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 45-1: THE CONFIGURATION FILE’S HEADER FILE
#ifndef _CONFIGURATIONFILE_H_ #define _CONFIGURATIONFILE_H_
#include <string> #include <vector> #include <fstream> #include <map> #include <list>
using namespace std;
class ConfigurationFile
{
public:
ConfigurationFile(const char *strFileName);
virtual ~ConfigurationFile(void); bool read(void);
bool |
hasValue( const char |
*key |
); |
|
string |
getValue( const |
char |
*key |
); |
void |
setValue( const |
char |
*key, const |
char *value );
protected:
virtual void get_token_and_value(); virtual char eat_white_and_comments(bool traverse_ newlines=true);
virtual bool advance_to_equal_sign_on_line(); virtual void makeLower (string &instring);
protected: |
|
fstream |
m_in; |
string |
m_token; |
string |
m_value; |
string |
m_sConfigFile; |
typedef pair <string, string> String_Pair;
map<string, string> m_ConfigEntries;
};
#endif
This file contains the definition of the class; it contains no code for manipulating the data. The header file acts as the interface for other applications to use the class, as we will see. It is best to separate your actual implementation code from your definition, as this helps emphasize the encapsulation concept of C++.
3. Save the source-code file.
4. In the code editor of your choice, create a new file to hold the definition for the configurationfile class.
252 Technique 45: Creating a Configuration File
In this example, the file is named
ConfigurationFile.cpp, although you can use whatever you choose.
5. Type the code from Listing 45-2 into your file.
LISTING 45-2: THE CONFIGURATION FILE SOURCE CODE.
#include “ConfigurationFile.h” #include <errno.h>
#include <algorithm> #include <sstream> #include <iostream> #include <string>
template <class T> bool from_string(T &t,
const std::string &s, std::ios_base &
(*f)(std::ios_base&))
{
std::istringstream iss(s); return !(iss>>f>>t).fail();
} |
|
|
class StringUtil |
|
1 |
{ |
|
public: StringUtil() {}
~StringUtil() {}
//Find the given string in the source string and replace it with the
//“replace” string, everywhere instances of that string exist.
static void findandreplace( string& source, const string& find, const string&
replace )
{
size_t j;
for (;(j = source.find( find )) != string::npos;)
{
source.replace( j, find.length(), replace );
}
}
//The following function returns a string with all-uppercase characters.
static string makeUpper( const string& instring)
{
string temp=instring;
transform( temp.begin(), temp.end(), temp.begin(), ::toupper );
return temp;
}
//The following function returns a string with all-lowercase characters.
static string makeLower( const string& instring)
{
string temp;
transform( temp.begin(), temp.end(), temp.begin(), ::tolower );
return temp;
}
static bool contains( const string& source, const char *find )
{
return ( 0!=strstr(source. c_str(),find) );
}
static string pad( const string& instring, char padchar, int length )
{
string outstring = instring;
for ( int i=(int)outstring.length(); i<length; ++i )
outstring += padchar;
return outstring;
}
//Trim the given characters from the beginning and end of a string.
//the default is to trim whitespace. If the string is empty or contains
//only the trim characters, an empty string is returned.
static string trim( const string &instring,
const string &trimstring=string(“ \t\n”))
Creating the Configuration-File Class |
253 |
{
if (trimstring.size()==0) return instring;
string temp=””;
string::size_type begpos=instring.find_first_not_of (trimstring); if (begpos==string::npos)
{
return temp;
}
else
{
string::size_type endpos=instring.find_last_not_of (trimstring); temp=instring.substr(begpos,
endpos-begpos+1);
}
return temp;
}
//Convert the string to an int. Note that a string exception is thrown if
//it is invalid.
static int toInt(const string & myInString)
{
int i=0;
string inString = trim(myInString);
if( !from_string<int>(i, inString, std::dec) )
{
string exceptionText = “StringUtils::toInt() - Not an integer: “ + inString; throw exceptionText;
}
// Time to run some more checks.
for (unsigned int j=0; j < inString.length(); j++)
{
if ( !isNumeric(inString[j]) )
{
if (j==0 && inString[j] ==’-’)
{
continue;
}
else
{
string exceptionText = “StringUtils::toInt() - Not an integer: “ + inString;
throw exceptionText;
}
}
}
(continued)
254 Technique 45: Creating a Configuration File
LISTING 45-2 (continued)
return (i);
}
//Convert the string to an int. Note: A string exception is thrown if
//it is invalid.
static float toFloat(const string & myInString)
{
float f=0;
string inString = trim(myInString);
if( !from_string<float>(f, inString, std::dec) )
{
string exceptionText = “StringUtils::toFloat() - Not a float: “ + inString; throw exceptionText;
}
// Now it runs some more checks. int dec_count=0;
for (unsigned int j=0; j < inString.length(); j++)
{
if ( !isNumeric(inString[j]) )
{
if (j==0 && inString[j] ==’-’)
{
continue;
}
else if (inString[j]==’.’)
{
dec_count++;
if (dec_count > 1)
{
string exceptionText = “StringUtils::toFloat() - Not a float: “ + inString;
throw exceptionText;
}
continue;
}
else
{
string exceptionText = “StringUtils::toFloat() - Not a float: “ + inString; throw exceptionText;
}
}
}
return (f);
}
Creating the Configuration-File Class |
255 |
//Returns true if the character is numeric. static bool isNumeric(char c)
{
return (‘0’ <= c && c <= ‘9’);
}
//Replace environment variables in the string with their values.
//Note: environment variables must be of the form ${ENVVAR}. static string substituteEnvVar( const string &myInString )
{
string outString=””; char variable[512];
const char *s = myInString.c_str(); while(*s!=0)
{
if (*s==’$’ && *(s+1)==’{‘)
{
// When you’ve found beginning of variable, find the end. strcpy(variable,s+2);
char *end = strchr (variable,’}’); if (end)
{
*end=’\0’;
char *cp = (char *)getenv(variable); if (cp)
outString += (char *) getenv(variable); s = strchr(s,’}’);
}
else
{
outString += *s;
}
}
else
{
outString += *s;
}
s++;
}
return outString;
}
};
(continued)
256 Technique 45: Creating a Configuration File
LISTING 45-2 (continued)
ConfigurationFile::ConfigurationFile( const char *strConfigFile )
{
if ( strConfigFile )
m_sConfigFile = strConfigFile;
}
ConfigurationFile::~ConfigurationFile()
{
}
bool ConfigurationFile::read()
{
m_in.open(m_sConfigFile. c_str(),ios::in);
if (m_in.fail())
{
return false;
}
while (!m_in.eof())
{
//--------------------------------------------------------
//Get a token and value.
//This gives values to member vars: m_token and m_value.
//----------------------------------------------------------
get_token_and_value();
if ( m_token.length() ) m_ConfigEntries.insert( String_
Pair(m_token, m_value) );
}
m_in.close();
return true;
}
void ConfigurationFile::get_token_and_ value(void)
{
char token[1024]; char ch;
bool found_equal=false;
int i=0; eat_white_and_comments(); while(!(m_in.get(ch)).fail())
{
if ((ch != ‘\t’))
{
2
3
Creating the Configuration-File Class |
257 |
if ( (ch == ‘=’) || (ch == ‘ ‘) || (ch == ‘\n’) || (ch == ‘\r’) || (ch == ‘\t’))
{
if (ch == ‘=’)found_equal=true; break;
}
token[i++]=ch;
}
}
if (i==0)
{
// It didn’t find a token, in this case. m_token=””;
m_value=””; return;
}
// Null-terminate the token that was found. token[i++]=’\0’;
m_token = token; makeLower(m_token);
// Advance to the equal sign, if need be. if (!found_equal)
{
if (!advance_to_equal_sign_on_line())
{
// The token had no value. m_token=””;
m_value=””; return;
}
}
// Get the token’s value. i=0;
char c = eat_white_and_comments(false);
if ( c != ‘\n’ )
{
i=0; while(!(m_in.get(ch)).fail())
{
if ((ch == ‘\t’) || (ch == ‘\r’) || (ch == ‘\n’) || (ch == ‘#’) )
{
while (ch!=’\n’)
{
if (m_in.get(ch).fail()) break;
}
(continued)
258 Technique 45: Creating a Configuration File
LISTING 45-2 (continued)
break;
}
else
{
token[i++]=ch;
}
}
}
if (i==0)
{
// This token had no value. m_value=””;
}
else
{
token[i++]=’\0’; m_value=token;
//Remove leading/trailing spaces. m_value = StringUtil::trim(m_value);
//Strip leading and trailing quotes, if there are any. if ( m_value[0] == ‘“‘ )
m_value = m_value.substr( 1 );
if ( m_value[ m_value.length() -1 ] == ‘“‘ )
m_value = m_value.substr( 0, m_value.length()-1 );
}
}
bool ConfigurationFile::advance_to_equal_sign_on_line()
{
char ch;
bool found_equal=false;
while ( !(m_in.get(ch)).fail() )
{
if ((ch==’\r’)||(ch==’\n’)) break; if (ch == ‘=’)
{
found_equal=true; break;
}
}
return found_equal;
}
char ConfigurationFile::eat_white_and_comments
Creating the Configuration-File Class |
259 |
(bool traverse_newlines)
{
char ch;
bool in_comment;
in_comment = false;
while (!(m_in.get(ch)).fail()) if (ch == ‘#’)
in_comment = true; else if (ch == ‘\n’)
{
in_comment = false;
if (!traverse_newlines)
{
return(ch); // Stop eating.
}
}
else if ((!in_comment) && (ch != ‘ ‘) && (ch != ‘\t’) && (ch != ‘\r’))
{
m_in.putback(ch); return 0;
}
return 0;
}
void ConfigurationFile::makeLower (string &instring)
{
for(unsigned i=0; i < instring.size(); i++)
{
instring[i] = tolower(instring[i]);
}
}
bool ConfigurationFile::hasValue( const char { *key ) 4
bool bRet = false; std::string sKey = key; makeLower( sKey );
if ( m_ConfigEntries.find( sKey.c_str() ) != m_ConfigEntries.end() )
{
bRet = true;
}
return bRet;
}
string ConfigurationFile::getValue( const char *key )
{
std::string sKey = key; makeLower( sKey );
if ( m_ConfigEntries.find( sKey. c_str() ) != m_ConfigEntries.end() )
{
std::map<string, string>::iterator iter;
iter = m_ConfigEntries.find(sKey.c_str());
return (*iter).second;
}
return “”;
}
void ConfigurationFile::setValue( const char *key, const char *value )
{
std::string sKey = key; makeLower( sKey );
m_ConfigEntries[sKey] = value;
}
Our source code above breaks down into three general pieces. First, we separate out all of the utility routines that work with strings and characters and place them in the StringUtil utility
class (shown at the line marked with |
1). Next, |
||
we have the actual configuration-file class, |
|||
shown at the line marked with 2. This class |
|||
manages the storage and processing |
of the file. |
||
The processing is done in the read function, |
|||
shown at |
3, and the storage functions begin |
||
with the line marked |
4. As you can see, the |
||
routine simply reads |
in a line from the input file |
and separates it into two pieces, divided by an equal sign. Comments, which are lines that are either blank or marked with a pound sign (‘#’) are ignored. Everything to the left of the equal sign is considered to be the “tag,” while everything to the right of the equal sign is considered to be the “value.” Tag and value pairs make up the configuration data. The retrieval routines work by allowing the user to see if a given tag is defined, and if so to retrieve its value.
6. Save the source file in the source code editor.