Previous Up Next

Chapter 1  Introduction

1.1  Purpose of this Manual

This manual provides a reference for the application programming interface (API) of Config4Cpp. This manual does not provide a beginner’s tutorial on Config4Cpp. You can find such a tutorial in the Config4* Getting Started Guide.

The rest of this chapter discusses the principles that underpin the API of Config4Cpp. Knowledge of these principles makes it easier to understand the API. The chapters that follow discuss the API of individual classes.

1.2  Namespace

All the classes of Config4Cpp are defined in the config4cpp namespace. For conciseness, the config4cpp:: namespace prefix is not explicitly stated in the discussion of classes and operations in this manual.

1.3  Memory management

The API is designed so that ownership of heap-allocated memory is not transferred from Config4Cpp to application code, or vice versa. For example, if an application calls lookupString(), the application should not delete the returned string when it is finished processing it. There are two motivations behind this memory management policy.

The first motivation is to simplify the API and, in doing so, minimize the chances of memory leaks in applications that use Config4Cpp.

The second motivation comes from the Microsoft Visual C++ compiler. This compiler does something unusual: it implements the new and delete operators with one algorithm if you compile in debug mode, but implements those operators with an incompatible, algorithm if you compile in release (that is, non-debug) mode. If an application contains some files that were compiled in debug mode and other files that were compiled in release mode, then the application might crash if memory allocated with the debug version of new is freed using the release version of delete, or vice versa. Typically, this problem occurs when application code is compiled in, say, debug mode, but is linked against a third-party library that was compiled in release mode. Many vendors of third-party libraries work around this problem by supplying both debug and release versions of their libraries. The Config4Cpp library takes a different approach to avoiding mismatched versions of new and delete. Put simply, any memory allocated inside Config4Cpp is later freed from inside Config4Cpp. By doing this, it is not necessary to compile both debug and release versions of the Config4Cpp library.

1.4  Portability

One of the design goals of Config4Cpp is portability. Not only should Config4Cpp compile on many operating systems; it should also compile with both new and older C++ compilers. Some older C++ compilers do not fully support the standard C++ library. Because of this, the implementation of Config4Cpp does not use anything in the standard C++ library. This has some knock-on effects, as I now discuss.

First, the implementation of Config4Cpp does not use the IO streams library to read a configuration file. This is because using IO streams means using either classic IO streams (that is, <iostream.h>), or standard IO streams (that is, <iostream>). In many compilers, these are not link compatible so if your application uses classic IO streams, then you cannot link your application with a third-party library that uses standard IO streams. It would have been possible for Config4Cpp to use conditional compilation to let a person compiling Config4Cpp specify if it should use classic or standard IO streams. However, it turns out to be just as easy for Config4Cpp to use <stdio.h> in the standard C library to read configuration files, and so bypass unpleasant issues associated with the choice between classic or standard IO streams.

Second, the implementation of Config4Cpp does not use any types in the standard template library (STL). Nor does Config4Cpp define any template types itself. One reason for this is to avoid the portability headache of dealing with platform-specific build issues when using template types. Another reason is that some projects wishing to use Config4Cpp might be using a relatively old C++ compiler that does not support template types. The amount of code in Config4Cpp could have been reduced by making use of std::string, std::vector and std::map. However, for the sake of portability, the Config4Cpp library implements its own utility classes that implement similar functionality.

Most of the external APIs used by the implementation of Config4Cpp are functions in the standard C library, which is much more portable than the C++ library. However, it has not been possible to write Config4Cpp entirely using only portable APIs: a few platform-specific APIs have been required too; these are discussed in the Config4* Maintenance Guide.

1.5  Error Reporting

The Config4Cpp parser does not make any attempt at error recovery. Instead, it stops at the first error it encounters, and reports the error by throwing an exception. The lack of error recovery helps to keep the implementation of the parser simple. It also simplifies the public API because the ability to report multiple parsing errors would have required a more complex API.

1.6  Specifying Scoped Names

Many of the operations in Config4Cpp work with scoped names, for example, foo_srv.log.dir. Typically, the first part, foo_srv, is a scope for a particular application, and the remainder, log.dir, is a configuration variable used by that application. It can be useful to change the application name (foo_srv) without needing to change lots of code. It would not be possible to do this if foo_srv.log.dir appeared in application code. Instead, it is best for the two parts of a name to be specified separately, and then merged to form the fully-scoped name.

It would be tedious for developers to do such merging manually. To avoid this, the Config4Cpp operations that work on scoped names take two string parameters. Internally, the operations merge the strings to obtain a fully-scoped name. For example, you can access the value of foo_srv.log.dir with the following statement:

logDir = cfg->lookupString("foo_srv", "log.dir");

The intention is that an application can declare a variable called, say, scope and initialize its value from a command-line argument. Then the application can access configuration information from within that scope by using code like that shown below:

logDir = cfg.lookupString(scope, "log.dir");

By rerunning an application with a different command-line argument, you can change the scope used to configure the application. This provides a lot of flexibility. For example, you might have one configuration scope for running an application without debugging diagnostics, and another scope that enables debugging diagnostics. Alternatively, you might have a separate scope for each instance of a replicated server application.

1.7  Support Classes

I explained in Section 1.4 that, to increase its portability, Config4Cpp avoids use of template types, including those in the standard C++ library. Instead, Config4Cpp implements two support classes that provide functionality somewhat similar to that provided by some classes in the standard C++ library.

1.7.1  The StringBuffer Class

Instead of using the std::string and std::stringstream classes, Config4Cpp defines its own StringBuffer class that provides similar-ish functionality. The public API of this class is shown in Figure 1.1.

Figure 1.1: The StringBuffer class
// Access with #include <config4cpp/StringBuffer.h>
// or          #include <config4cpp/Configuration.h>

class StringBuffer
{
public:
    StringBuffer();
    StringBuffer(const char * str);     // deep copy
    StringBuffer(const StringBuffer &); // deep copy
    ~StringBuffer();

    const char *   c_str() const;
    char           lastChar() const;
    int            length() const;
    void           empty();
    void           deleteLastChar();
    StringBuffer & append(const StringBuffer & other); // deep copy
    StringBuffer & append(const char * str);           // deep copy
    StringBuffer & append(int val);
    StringBuffer & append(float val);
    StringBuffer & append(char ch);

    StringBuffer & operator=(const char * str);           // deep copy
    StringBuffer & operator=(const StringBuffer & other); // deep copy
    char           operator[](int index) const;
    char &         operator[](int index);
    StringBuffer & operator<<(const StringBuffer & other);// deep copy
    StringBuffer & operator<<(const char * str);          // deep copy
    StringBuffer & operator<<(int val);
    StringBuffer & operator<<(float val);
    StringBuffer & operator<<(char ch);
};

The default constructor initialises the StringBuffer to maintain an empty C-style string (""). The constructor taking a const char* parameter takes a deep copy of that parameter. Likewise, the copy constructor makes a deep copy of the string contained inside its parameter.

You can grow the C-style string inside a StringBuffer by calling the overloaded append() operation or by using the overloaded << operator.

You can replace the C-style string contained in a StringBuffer by using the overloaded assignment operator ("=").

The c_str() operation returns a pointer to the C-style string contained inside the StringBuffer.

The empty() operation resets the StringBuffer to having an empty C-style string.

The length() operation returns the length of the C-style string contained inside the StringBuffer.

The lastChar() operation returns the final (non-null) character of the C-style string contained in the StringBuffer if the string is not empty; otherwise, it returns ’\0’.

The deleteLastChar() operation removes the last character from the C-style string contained in the StringBuffer. It is an error to call this operation on an empty StringBuffer.

The StringBuffer class is used mainly by the internals of Config4Cpp. However, StringBuffer is exposed to application programmers via parameters to a small number of public operations. In such cases, it is always used as an “out” or “in-out” parameter, so that application code does not have to explicitly delete a heap-allocated string allocated by the internals of Config4Cpp.

1.7.2  The StringVector Class

Config4Cpp defines its own StringVector class that provides functionality similar-ish to that provided by std::vector<std::string>. The public API of StringVector is shown in Figure 1.2.

Figure 1.2: The StringVector class
// Access with #include <config4cpp/StringVector.h>
// or          #include <config4cpp/Configuration.h>

class StringVector
{
public:
    StringVector(int initialCapacity = 10);
    StringVector(const StringVector &);    // deep copy
    ~StringVector();

    void           ensureCapacity(int size);
    void           add(const char * str);
    void           add(const StringBuffer & strBuf);
    void           add(const StringVector & other); // adds all items

    void           c_array(const char**& array, int& arraySize) const;
    const char **  c_array() const;

    int            length() const;
    const char *   operator[](int index) const;
    StringVector & operator=(const StringVector & other); // deep copy

    void           replace(int index, const char * str); // deep copy
    void           removeLast();
    void           empty();

    void           sort();
    bool           bSearchContains(const char * str) const;
};

The StringVector class provides a simplification wrapper around a null-terminated array of C-style strings. The default constructor initialises the StringVector to maintain an empty, null-terminated array. The copy constructor makes a deep copy of all the C-style strings in the parameter.

Strings are added to a StringVector by calling the overloaded add() operation, which grows the internal array of the StringVector if necessary. If you know in advance how many strings you will add, then you can invoke ensureCapacity() to prevent repeated re-allocations of the internal array.

The c_array() operation provides access to the internal array of C-style strings. The returned array is always null-terminated. This operation is overloaded to provide access to the null-terminated array of strings with and without a count of the number of strings in the array.

The length() operation returns the number of C-style strings currently in the array. You can use operator[] to get read-only access to individual items of the array, and call replace() to replace an individual item.

The removeLast() operation removes the string at the end of the array, while empty() resets the StringVector back to being an empty, null-terminated array.

The sort() operation uses strcmp() as its comparison function to sort the strings in the array.

The bSearchContains() operation performs a binary search (using strcmp() as its comparison function) on the array of strings, which are assumed to be sorted. If the target string is found, then this operation returns true; otherwise, it returns false.

The StringVector class is used mainly by the internals of Config4Cpp. However, StringVector is exposed to application programmers via parameters to some public operations, such as lookupList(), insertList(), listFullyScopedNames() and listLocallyScopedNames().


Previous Up Next