polymorphnick

tech thoughts by Nicholas Solter

Browsing Posts in C++

I have to admit that when I was writing Professional C++ I never imagined this. A couple weeks ago, I received a few copies from my agent, so thought I’d take some pictures of the four different editions I’ve had the pleasure of holding in my hands.

Left to right: US/Canadian/UK edition, Russian edition, Indian (English-language) Edition, Chinese Edition

Bottom to top: US/Canadian/UK edition, Russian edition, Chinese Edition, Indian (English-language) Edition

A co-worker recently asked me to look at an interesting C++ compilation problem. The constructor for one of her classes refused to compile until she simply changed one of the parameter names! Why should the parameter name matter?

Here’s a much-simplified version of the class, with a trivial main(), removing proprietary information (this code isn’t yet open-source).


#include
#include
#include
class myclass {
public:
   myclass(char *s_net);
};
myclass::myclass(char *s_net)
{
   // do stuff
}
int main()
{
   // do stuff
}

The Sun Studio 10 compiler gives the following errors when attempting to compile the translation unit:


“test.cc”, line 7: Error: No direct declarator preceding “(”.
“test.cc”, line 10: Error: myclass is not a static data member.
“test.cc”, line 10: Error: Badly formed expression.

Changing the name of the only parameter to the myclass constructor to something else like net allows the code to compile with no errors.

What’s going on?

It turns out that s_net is defined in netinet/in.h as follows


#define s_net _S_un._S_un_b.s_b1 /* OBSOLETE: network */

So when the preprocessor makes its substitutions prior to the actual compilation, the symbol s_net is replaced with something like _S_un._S_un_b.s_b1. (There are actually a few more substitutions, as we’ll see momentarily). We can see this ourselves by using the -P option to CC to run the test program through just the preprocessor. This gives, in part:


class myclass {
public :
myclass ( char * S_un . S_un_b . s_b1 ) ;
} ;
myclass :: myclass ( char * S_un . S_un_b . s_b1 )
{
}

That obviously won’t compile!

This example demonstrates two problems. The first is the use of globals, specifically #defines. Who would expect a generic symbol like s_net to be #defined in a standard header file? It would be bad enough if it were simply a global variable, but that at least would probably not have caused a problem in this case, because the use of s_net in the constructor would hide the global s_net. However, the preprocessor actually search/replaces the #define term so that the compiler never gets a chance to see s_net as the parameter name in the constructor.

The second problem is that the preprocessor does this substitution and other modifications such that the code for which the compiler is giving a warning is not the code that you see when you look at your source file. This can make debugging difficult.

Moral of the story: Use the preprocessor judiciously!

Why Do I Need Smart Pointers?

Working with dynamically allocated memory in languages like C++ can be difficult. If you allocate some memory and forget to free it, you get a memory leak. But, if you free it more than once, you get memory corruption. Not surprisingly, memory management errors are a significant source of bugs in many C++ programs.

Now imagine if you could dynamically allocate memory and use it in your program without ever worrying about freeing it! No, I’m not suggesting you switch to Java. You can obtain this ease-of-use in C++ too, simply by using smart pointers.

How Do Smart Pointers Work?

The fundamental insight behind smart pointers is that the stack is safer than the heap. Variables on the stack are automatically destructed and freed when they go out of scope. Variables on the heap are freed only by an explicit call to delete. The trick is to represent a heap-based variable, accessed through a traditional dumb pointer, by a stack-based variable. Smart pointers accomplish this sleight-of-hand by encapsulating a dumb pointer in an object that is kept on the stack. When this object goes out of scope, its destructor takes care of freeing the memory associated with the underlying dumb pointer.

Reference-counting smart pointers take this technique a step further and track all the copies of the smart pointer object. This enhancement handles the aliasing problem, when more than one object or piece of code contains copies of the same pointer. Only when the last copy of the object goes out of scope is the underlying dumb pointer freed.

Smart Pointers in Action

Some concrete examples should help to show you how reference-counting smart pointers work. Smart pointers in C++ are typically implemented as template classes. This example uses the SuperSmartPointer template class from Chapter 25 of Professional C++. Feel free to download SuperSmartPointer.h and SuperSmartPointer_in.h and play around with it. In this post, I won’t get into the implementation details of the SuperSmartPointer – just treat it as a black box for now.

The examples that follow use a simple class that just outputs a line in its constructor and destructor and provides one other public method:

class Nothing
{
public:
   Nothing() { cout << “ctor\n”; }
   ~Nothing() { cout << “dtor\n”; }
   void SaySomething() { cout << “Something\n”; }
};

First, consider the following function:

void TestNothing()
{
   Nothing *myNothing = new Nothing();
   myNothing->SaySomething();
}

The output of a program calling the function is:

ctor
Something

One call to the constructor, but no calls to the destructor. Memory leak! The function never calls delete on the myNothing pointer.
Now let’s use the SuperSmartPointer:

void TestNothing()
{
   Nothing *myNothing = new Nothing();
   SuperSmartPointer<Nothing> mySmartNothing(myNothing);
   mySmartNothing->SaySomething();
}

Note that mySmartNothing is declared as a stack-based object. The syntax is a bit confusing because SuperSmartPointer is a template class, and so requires the pointer type Nothing to be specified in the <> angle brackets. For more detail on class templates, see this tutorial, or Chapter 11 of Professional C++.
When the mySmartNothing object goes out of scope, its destructor is called, which frees the myNothing pointer. The output of the program calling this function now is:

ctor
Something
dtor

No memory leak!

In addition to showing you how using a smart pointer can prevent a memory leak, the example above showed you how to use a smart pointer to get at the memory location to which it points. As you saw, you can dereference SuperSmartPointer objects with -> just as you would dereference normal dumb pointers. You can also use *. That’s because the SuperSmartPointer template class uses C++ operator overloading to provide those dereferencing operators.

Reference-Counting Smart Pointers in Action

The example above barely scratches the surface of the power of reference-counting smart pointers. Consider a program that passes a pointer between various functions:

void FunctionB(Nothing *myNothing)
{
   // do something
}
Nothing *FunctionC(Nothing *myNothing)
{
   // do something
   return (new Nothing());
}
void FunctionA()
{
   Nothing *myNothing = new Nothing();
   // do something
   FunctionB(myNothing);
   myNothing = FunctionC(myNothing);
}

The output from this program is:

ctor
ctor

Two object constructions and no destructions! The problem, of course, is that the program never calls delete on the objects it creates, perhaps because it isn’t clear which function is responsible for freeing the memory.
Now let’s look at a revised version using SuperSmartPointer:

void FunctionB(SuperSmartPointer<Nothing> mySmartNothing)
{
   // do something
}
SuperSmartPointer<Nothing>
FunctionC(SuperSmartPointer<Nothing> mySmartNothing)
{
   // do something
   SuperSmartPointer<Nothing> myNewSmartNothing(new Nothing());
   return (myNewSmartNothing);
}
void FunctionA()
{
   Nothing *myNothing = new Nothing();
   SuperSmartPointer<Nothing> mySmartNothing(myNothing);
   // do something
   FunctionB(mySmartNothing);
   mySmartNothing = FunctionC(mySmartNothing);
}

Notice that there are no explicit calls to delete in this version either. However, the output of the program is:

ctor
ctor
dtor
dtor

No memory leaks! That’s because the dumb pointers are encapsulated by smart pointer objects on the stack. When the last smart pointer object for a given dumb pointer goes out of scope, its destructor frees the underlying dumb pointer.

When using smart pointers, always pass the smart pointer object by value, not by reference, so that the reference count of the underlying dumb pointer can be incremented. If you try to pass the smart pointer by reference, such as by playing with a pointer to the smart pointer object, the reference counting will get confused and won’t work properly.

Conclusion

Hopefully the above examples showed you some of the power of reference-counting smart pointers. I encourage you to try them out in your next C++ program!

The following tutorial is adapted from Professional C++ Chapter 11, “Writing Generic Code with Templates.”

What are Templates?

Most computer languages, including C++, provide support for procedures or functions that allow you to write algorithms that are independent of specific values and can thus be reused for many different values. For example, the sqrt() function in C and C++ calculates the square root of a value supplied by the caller. A square root function that calculated only the square root of one number, like four, would not be particularly useful! The sqrt() function is written in terms of a parameter, which is a stand-in for whatever value the caller passes. Computer scientists say that functions parameterize values.

Templates (called Generics in Java) take the concept a step further to allow you to parameterize on types as well as values. Recall that types in C++ include primitives such as int and double, as well as user-defined classes. With templates, you can write code that is independent not only of the values it will be given, but of the types of those values as well! For example, instead of writing separate queue classes to store ints, Packets, or any other object, you can write one queue class definition that can be used for any of those types.

C++ supports both class templates and function templates. This tutorial will cover the basics of class templates.

Writing a Class Template

Suppose you want a generic two-dimensional grid class that you could use as a game-board, as a spreadsheet, or for anything else that requires a 2-D grid. In order to make it general-purpose, you should be able to store elements of any type in the grid. The class should provide methods to set and retrieve an element at any location in the grid, and could be implemented as a two-dimensional array.

The Grid Class Definition

Here’s the class outline:

template <typename T>
class Grid
{
};

The first line says that the following class definition is a template on one type. Note that, for historical reasons, you can use the keyword class in place of typename. You might see this usage in programs or books, but it means exactly the same thing as using typename. Just as we use variables to represent function parameters, we use variables to represent type parameters. In this case we call the type parameter T, but there’s nothing special about that name. You could call it someVariableName if you wanted. In the same way that a function body can refer to the parameter variable names, a class definition can refer to the type variable name.

Here’s the class definition with the constructors, destructor, and assignment operator filled in:

template <typename T>
class Grid
{
public:
   static const int kDefaultWidth = 10;
   static const int kDefaultHeight = 10;
   Grid(int inWidth = kDefaultWidth, int inHeight = kDefaultHeight);
   Grid(const Grid<T>& src);
   ~Grid();
   Grid<T>& operator=(const Grid<T>& rhs);
};

Note that the copy constructor takes as its src argument const Grid<T>&, not const Grid&. That’s because, in a class template, what you used to think of as the class name Grid is actually the template name. When you want to talk about actual Grid classes or types, you discuss them as instantiations of the Grid class template for a certain type. You haven’t specified the real type yet, so you must use a stand-in template parameter, T, for whatever type might be used later. Thus, when you need to refer to a type for a Grid object as a parameter to, or return value from, a method, you must use Grid<T>. You can see this change also in the assignment operator.

Here’s the full class definition:

template <typename T>
class Grid
{
public:
   Grid(int inWidth = kDefaultWidth, int inHeight = kDefaultHeight);
   Grid(const Grid<T>& src);
   ~Grid();
   Grid<T>& operator=(const Grid<T>& rhs);
   void setElementAt(int x, int y, const T& inElem);
   T& getElementAt(int x, int y);
   const T& getElementAt(int x, int y) const;
   int getHeight() const { return mHeight; }
   int getWidth() const { return mWidth; }
   static const int kDefaultWidth = 10;
   static const int kDefaultHeight = 10;
protected:
   void copyFrom(const Grid<T>& src);
   T** mCells;
   int mWidth, mHeight;
};

Note that setElementAt() takes a reference to an element of type T. This type T is a placeholder for whatever type the user specifies when she instantiates the template (creates an object of the class for a specific type). Similarly, getElementAt() returns a reference to type T, and mCells is a pointer to a two-dimensional array of T elements.

The Grid Class Implementation

The key points when writing template classes are that the template <typename T> specifier must precede each method definition, and the class name before the :: is Grid<T> not Grid. For example, the constructor looks like this:

template <typename T>
Grid<T>::Grid(int inWidth, int inHeight) : mWidth(inWidth), mHeight(inHeight)
{
   mCells = new T* [mWidth];
   for (int i = 0; i < mWidth; i++) {
      mCells[i] = new T[mHeight];
   }
}

Note the use of T as a stand-in for whatever type the user specifies when she instantiates the template. If it confuses you, just substitute a real type like int in your head to make sense of the code.

Here’s another method implementation:

template <typename T>
T& Grid<T>::getElementAt(int x, int y)
{
   return (mCells[x][y]);
}

You can download the full class definition and implementation here.

Using the Grid Template

When you want to create grid objects, you cannot use Grid alone as a type; you must specify the type that will be stored in that grid. Here is an example:

#include “Grid.h”
int main(int argc, char** argv)
{
   Grid<int> myIntGrid; // declares a grid that stores ints
   myIntGrid.setElementAt(0, 0, 10);
   int x = myIntGrid.getElementAt(0, 0);
   Grid<int> grid2(myIntGrid);
   Grid<int> anotherIntGrid = grid2;
   return (0);
}

Note that the type of mIntGrid, grid2, and anotherIntGrid is Grid<int>. You cannot store doubles or strings in these grids; the compiler will generate an error if you try to do so.

The type specification is important: neither of the following two lines compiles:

   Grid test; // WILL NOT COMPILE
   Grid<> test; // WILL NOT COMPILE

If you want to declare a function or method that takes a Grid object, you must specify the type stored in that grid as part of the Grid type:

void processIntGrid(Grid<int>& inGrid)
{
   // body omitted for brevity
}

You can also dynamically allocate Grid template instantiations on the heap:

   Grid<int>* myGridp = new Grid<int>();
   myGridp->setElementAt(0, 0, 10);
   x = myGridp->getElementAt(0, 0);
   delete myGridp;

You can download the test file here.

Distributing Template Code between Files

Because they are “templates” for the compiler to generate the actual methods for the instantiated types, both template class definitions and method definitions must be available to the compiler in any source file that uses them. In this sense, methods of a template class are similar to inline methods. In order to obtain this inclusion, you can put the method definitions directly in the same header file as the class definition itself, or you can put the method definitions in a separate header or source file and #include that file at the bottom of the class definition file.

I strongly disliked writing when I was in school. In fact, it probably wouldn’t be too strong to say I hated it. In college I actively avoided classes involving writing assignments. So I sometimes find it hard to believe that not only do I enjoy writing now, I was the lead author of an 838-page book on C++.

My co-author, Scott Kleper, wrote a great post about the process of writing Professional C++, so I won’t dwell on that here. Instead, I’d like to introduce a few of the features that we feel sets Professional C++ apart from other programming books. In future posts I’ll cover some of the particularly interesting aspects of the C++ language and C++ software engineering.

Scott and I believe that teaching C++ programming involves two things: teaching C++ and teaching programming. To that end, we tried to present the C++ syntax and feature-set in the larger context of software engineering and object-oriented methodologies. Specifically,

  • Emphasis on design, including object-oriented design, design themes of abstraction and reuse, design techniques such as smart pointers, and design patterns such as the singleton pattern.
  • Discussion of software engineering methodologies such as extreme programming.
  • Focus on style. We include an entire chapter on C++ style, and call attention to good stylistic practices throughout the book.
  • Inclusion of debugging and testing strategies. We devote over 50 pages to testing and debugging.
  • Discussion of “extra” topics such as writing efficient C++ code, mixing C++ with other languages, and using distributed objects.

In addition, we of course cover all the “usual” C++ stuff: objects, classes, inheritance, memory management, templates, operator overloading, the STL, I/O, exceptions, etc. Watch this space for a tutorial on smart pointers, coming soon!