advertisement

Print
C++ in a Nutshell

C++: Beyond the Standard Library

by Ray Lischner, author of C++ in a Nutshell
05/06/2003

Once you have mastered the C++ language and the standard library, what do you tackle next? The C++ community has plenty to keep you busy for years to come. To start with, you might take a look at the many C++ libraries that extend the standard. Sure, the C++ library has strings, I/O streams, containers, iterators, algorithms, and all that fun stuff. But it lacks much that is commonplace in modern computing: networks, GUIs, concurrency, and so on.

As a result, numerous C++ library projects have arisen to complete, correct, or extend the standard C++ library. Some were started even before the C++ standard was complete; others arose later. This article highlights a number of open source C++ libraries, including ACE, Loki, and Boost. (Many other projects exist, of course, but it is not possible to present them all in a single article.) If you have time to learn only one extra library, I recommend Boost.

Numerics

Two projects, Blitz++ and Matrix Template Library (MTL), provide high-performance numeric computing. In a way, they represent what valarray should have been.

The Blitz++ project was one of the early projects to take advantage of templates to shift part of the computing burden from runtime to compile time. It has powerful array and matrix classes, operators, and functions, with strides, subarrays, and so on.

Related Reading

C++ In a Nutshell
A Desktop Quick Reference
By Ray Lischner

One of the key optimizations is that arithmetic operators and mathematical functions involving Blitz++ arrays do not compute values immediately. Instead, they return expression objects. When an expression object is assigned to an array, the expression is computed, storing the results directly in the target of the assignment, without the need for large temporary arrays.

The MTL also uses templates to optimize performance. Its approach is more reminiscent of the STL, using containers (such as Matrix and Vector), iterators, and generic algorithms (such as transpose, dot (dot project), and so on).

ACE

ACE (ADAPTIVE Communication Environment) has been around for a long time, and provides a framework for networks, communication, and concurrency. It is well documented in two books: C++ Network Programming,: Mastering Complexity with ACE and Patterns, and C++ Network Programming: Systematic Reuse with ACE and Frameworks, both by Douglas Schmidt and Stephen Huston.

ACE is big. It does a lot, and it does a lot for you. It has concurrency, synchronization primitives, sockets, and related network classes. ACE also comes with TAO (The ACE Orb), for CORBA support. Most important, ACE is an integrated framework, where the networking and concurrency work together.

At the bottommost layer, ACE has a portable API to interface with the operating system (Windows, Linux, and many other UNIX variants). On top of this API is a set of C++ wrapper classes. The wrappers include synchronization primitives (such as Mutex and Semaphore), interprocess communication with sockets and pipes (such as ACE_SOCK_Connector, ACE_SOCK_FIFO, and ACE_SOCK_Addr), and concurrency (such as Future and Servant thread classes).

The next layer up is the main framework. The Reactor and Proactor classes dispatch messages in an event-oriented fashion, integrated with all the ACE synchronization classes. At this level, the ACE Orb is also integrated for CORBA.

You can download the source code from the main Distributed Object Computing web site at Washington University. (Follow the ACE link.)

Loki

Loki was invented by Andrei Alexandrescu and is documented in his book, Modern C++ Design. It pushes templates to their limits (and beyond for many compilers), showing the power of templates for policy-based programming and metaprogramming.

Unlike many other projects, Loki was developed on Windows, not Unix, and the files are DOS text files.

To see the benefits of policy-based programming, consider the problem of implementing a singleton class. A singleton class is a class for which no more than one instance can exist at runtime. Different needs for singletons give rise to different implementations. For example, sometimes you might want a single, static instance. Other times, you might want lazy instantiation, so the singleton object is not created until it is needed, but then is not destroyed until the program exits. Other times, you might want a dynamic instance that is destroyed when it is no longer needed, only to be recreated when it is needed again. A single class cannot easily meet these different demands, but a policy-based template can.

Loki declares the SingletonHolder template, which can be used to manage a singleton. You provide a type as a template parameter, and the SingletonHolder creates the singleton instance for you, which it returns from its Instance() member function. Other template parameters specify the policies that govern the singleton.

template
<
    typename T,
    template <class> class CreationPolicy = CreateUsingNew,
    template <class> class LifetimePolicy = DefaultLifetime,
    template <class> class ThreadingModel = SingleThreaded
>
class SingletonHolder
{
public:
    static T& Instance();
        
private:
    // Helpers
    static void MakeInstance();
    static void DestroySingleton();
        
    // Protection
    SingletonHolder();
        
    // Data
    typedef typename ThreadingModel<T*>::VolatileType PtrInstanceType;
    static PtrInstanceType pInstance_;
    static bool destroyed_;
};

The CreationPolicy determines how the singleton instance is created. Loki provides CreateUsingNew, CreateUsingMalloc, and CreateStatic. The latter constructs the singleton object in a static buffer. You can supply a custom creation policy by writing your own class template that provides static Create() and Destroy() member functions. The SingletonHolder takes care of the details of when Create() and Destroy() are called. Following is the CreateUsingNew policy:

template <class T> struct CreateUsingNew
{
    static T* Create()
    { return new T; }
        
    static void Destroy(T* p)
    { delete p; }
};

The LifetimePolicy determines the singleton's lifetime. The DefaultLifetime destroys the singleton object when the program exits (using an atexit handler). If the program attempts to refer to the singleton object after it has been destroyed, the LifetimePolicy object throws an exception. The PhoenixLifetime policy is similar, except it allows the singleton to be recreated after it has been destroyed.

The SingletonWithLongevity policy lets you control the relative lifetimes of multiple singletons. A longevity, in this case, is an unsigned integer. Objects with higher valued longevity are destroyed after objects with lower valued longevity. You must provide a free function, GetLongevity, that returns the longevity of an object.

Finally, the NoDestroy policy never destroys the singleton object. Use of this policy can result in memory or other resource leaks. To implement your own lifetime policy, write a class template that has static ScheduleDestruction and OnDeadReference functions. For example, the following code is the DefaultLifetime policy:

template <class T>
struct DefaultLifetime
{
    static void ScheduleDestruction(T*, void (*pFun)())
    { std::atexit(pFun); }

    static void OnDeadReference()
    { throw std::logic_error(std::string("Dead Reference Detected")); }
};

The final template parameter is the ThreadingModel, which can be SingleThreaded or ClassLevelLockable. The first does not perform any locking, and is suitable for single-threaded applications. ClassLevelLockable creates a lock for each class. Loki also has ObjectLevelLockable, but SingletonHolder is never instantiated because you use only the static Instance() function. Thus, you have no reason to use ObjectLevelLockable in this case.

To use SingletonHolder, start by declaring your class. Be sure to make the constructor private, with the creation policy as a friend, or otherwise ensure that the public cannot circumvent SingletonHolder and create arbitrary instances of your class. Then instantiate SingletonHolder with your type as the first template argument. You can use the default values for the remaining template arguments, or choose one of Loki's policy implementations, or write your own. The following is a simple example:

#include <cassert>
#include "Loki/Singleton.h"

class thingy {
    thingy() {}
    template <typename T> friend class Loki::CreateUsingNew;
public:
};

typedef Loki::SingletonHolder<thingy> single_thingy;

void foo(thingy& t)
{
    assert(&t == &single_thingy::Instance());
}

int main()
{
    foo(single_thingy::Instance());
}

Pages: 1, 2

Next Pagearrow