You are on page 1of 4

C++ in the Real World: Advice from the

Trenches

C++ has taken a lot of criticism: it's a big language that takes a long time to learn;
standardization has taken a long time, which has made it hard to write portable code;
newer languages, notably Java, draw more media attention. Still, among languages
that support an object-oriented style, C++ is by far the most heavily used, and its
usage is still growing rapidly. Why?

Some of the complexity of C++ is inherited from C, or results from its evolutionary
history, but most is a consequence of the language's power. For an easy problem, any
language will do; a hard problem demands a powerful language. Each feature of C++
exists because it has proven important for some area of industrial programming. With
the language standard nearly complete, compilers that implement most of the new
standard features are available now on most architectures.

Real-world programmers are more interested in problems than in languages: a


programming language is a way to solve a problem. When you use the right mix of
languages and language features, the solution to a problem is much easier to describe
and implement, with better results. C++ remains an essential tool for software
engineers not because anybody thinks it's the best possible language, but because it's a
single, portable language that works better than any alternative in each of several
areas. This article explores the strengths of C++, and how to exploit them in your
projects.

Why Use C++?

C++ is a general purpose programming language designed to make programming


more enjoyable for the serious programmer. [Stroustrup, 1985] For many uses, C++
is not the ideal language. You might prefer Tcl/Tk for writing a user interface, SQL
for relational database queries, Java for network programming, or Yacc for writing a
parser. C++ is used because it works well when the ideal language is (for whatever
reason) not available, and because it interfaces easily with the libraries and the other
languages you use.

It's no accident that you can interface C++ with almost any language interpreter or
library you find. You rarely find a big program written all in one language, or without
using libraries, so easy integration with other languages and libraries was a key design
goal.

Most problems have no specialized language to solve them; some because none has
(yet) been worth creating, and others because an interpreter would add too much
overhead. When you can't afford a specialized language for part of a problem, a
library may suffice. C++ was designed with libraries always in mind, and its most
useful features are those that help you write portable, efficient, easy-to-use libraries.
C++ as Glue

A solution for any big problem depends on solutions to a variety of smaller problems.
For some of those smaller problems you may find a specialized language or library
ideally suited for the job. For the rest, you must write your own code, and then
interface it with the other parts.

For example, in a business application you might use SQL to talk to a database server,
Postscript to talk to a printer, and Tcl/Tk for its user interface; and link with libraries
for fixed-precision arithmetic and encryption. In a multi-player game you might use
Java applets in the client machines, VRML3D to describe a virtual world, and Scheme
for describing non-player characters; and link with libraries for networking and
multithreading. The Microsoft NT kernel network driver contains a small Prolog
interpreter to help puzzle out network interface cards.

C++ is, by design, well suited to tying together the various parts of a programming
project. It has features specifically for calling libraries written in other languages, and
designers of other languages are careful to make them easy to connect to C++. It also
includes features to help organize big programs, and to keep libraries from interfering
with one another or with your main-line code. Most importantly, it imposes no upper
limit on the size of problem it can address; as your program evolves and grows, you
won't discover one day that the language just isn't up to the job any more.

The most familiar feature in C++ for interfacing with other languages is its C subset.
Anything you can do with C you can do with C++; hence, any interface designed for
C is usable from C++. Similarly, the asm(...) construct gives you access to lower-
level code and libraries that don't present a C interface. To call a C library, or to
export C interfaces, you just wrap the declarations in the extern "C" { ... }
block.

Interpreters are valuable when the desired semantics are unknown until runtime, or
when the compile-link cycle is just too slow. To get around performance bottlenecks,
most let you patch in C++ subroutines. In fact, some interpreters are written in C++
themselves, and their "built-in" features are implemented just that way. Some are also
available as a library, and let you create an interpreter object in your C++ program
whenever you need one. Such interpreters are called "extension languages"; good
ones include Python, Korn shell (ksh95), Tcl, and Scm (a Scheme variant).

To help improve program organization, more recent C++ compilers implement the
"namespace" feature. This lets you protect your programs against the chaos of global
names found in operating system interfaces, C libraries, and even other C++ libraries.
Namespaces let you group all your global names (functions, classes, and templates) in
a separate scope (or scopes), so you can combine libraries (your own or others')
without worrying about name collisions. Use them religiously.
Example 1: Compile-time Square Root Computation: ceil(sqrt(N))
// <root.h>:

template <int Size, int Low = 1, int High = Size>


struct Root;

template <int Size, int Mid>


struct Root<Size,Mid,Mid> {
static const int root = Mid;
};

template <int Size, int Low, int High>


struct Root {
static const int mean = (Low + High)/2;
static const bool down = (mean * mean >= Size);
static const int root = Root<Size,
(down ? Low : mean+1),
(down ? mean : High) >::root;
};

// User code:
// compute sqrt(N), use it for static table size

int table[Root<N>::root];

Example 2: Inline Vector Dot Product Expansion


// Given a forward declaration:
template <int Dim, class T>
struct dot_class;

// a specialized base case for recursion:


template <class T>
struct dot_class<1,T> {
static inline T dot(const T* a, const T* b)
{ return *a * *b; }
};

// the recursive template:


template <int Dim, class T>
struct dot_class {
static inline T dot(const T* a, const T* b)
{ return dot_class<Dim-1,T>::dot(a+1,b+1) +
*a * *b; }
};
// ... and some syntactic sugar:
template <int Dim, class T>
inline T dot(const T* a, const T* b)
{ return dot_class<Dim,T>::dot(a, b); }
// Then
int product = dot<3>(a, b);
// results in the same (near-)optimal code as
int product = a[0]*b[0] + a[1]*b[1] + a[2]*b[2

You might also like