![]() |
C++: Beyond the Standard Libraryby Ray Lischner, author of C++ in a Nutshell05/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 |
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 |
