C++ Notes

Chris Riesbeck
Last updated: July 3, 2009

This is a short review of special topics in C++ especially helpful for various assignments. These notes are a quick summary. They do not replace the longer discussions in the textbook, though they may override some.

Constants in C++

In C, especially older C, constants were defined with

#define PI 3.14159

Avoid these in C++. Use the const modifier instead, e.g.,

const double PI = 3.14159;

Even better is to define class-level constants, because they won't collide with constants from any other class. You declare class constants with static in the class declaration in the header file. You define them (no static) in the implementation file. The distinction between declare and define is critical: a declaration creates type information for the compiler, but does not cause any code or data values to be put in your program. Only a definition does that. C and C++ have a "one definition rule." You can have multiple declarations of something, as long as they're consistent, but not multiple definitions, even if they're identical.

For example, to get a class constant Circle::PI, you put this in circle.h,

class Circle {
public:
  static const double PI;
  ...
};

This would go in circle.cpp:

#include "circle.h"
const double Circle::PI = 3.14159;
...

If you put the definition in your header file, you'll probably get a "multiple definition" error.

Since the compiler doesn't see the value when compiling files that include circle.h, it can't completely optimize some code. Since this mostly affects integers, there's a special exception for them. You can declare their value in the header file. You still need to define them in the implementation file, or you'll get a "not defined" error from the linker, but you don't specify a value. For example, to define integer constants Date::JANUARY, Date::FEBRUARY and so on, put this in date.h:

class Date {
public:
  static const int JANUARY = 0;
  static const int FEBRUARY = 1;
  ...
};

and this in date.cpp:

#include "date.h"
const int Date::JANUARY;
const int Date::FEBRUARY;
...

Another kind of constant is the enumeration, e.g.,

enum { CLUB, DIAMOND, HEART, SPADE } CardSuit;
CardSuit suit = DIAMOND;

This defines constants for the values 0, 1, 2 and 3, from left to right.

Enumerations are also useful to label disconnected values, as in

enum { SOCKET_ERROR = 32, ILLEGAL_URL = 45, ... };

In C++, an enumeration is not an int! It can be easily converted to int, as needed, but, like char, it's a distinct type.

Exceptions

See also the CPlusPlus page on exceptions.

Signaling and handling errors can often make for complicated code. First, there's the signalling problem. If a function normally can return any number, what should it return if there's an error?

Then, there's the handling problem. If there is a value that a function can return for an error, e.g., -1, then every piece of code that uses that function has to check for -1, and, probably, return -1 itself until eventually some outer function is reached that can respond to the problem, e.g., by asking the user for different input.

These problems led to the invention of exceptions, which appear in C++ and other languages.

The two key syntactic forms are throw and try-catch. throw is like a special kind of return. Throwing a value immediately exits every function that is being executed, until control returns to a try-catch block that is waiting for the type of data being thrown.

In C++, you can throw anything. So, an easy thing is to use integers for error codes, and throw them when something bad is discovered somewhere. Most of your functions don't have to worry about error codes at all. They stay nice and simple. A function that discovers a problem just calls throw some integer. It doesn't have to worry about special return values.

To handle the exception, when it occurs, you write top-level code like this:

try {
 ...code that might, somewhere, way deep, throw an integer...
}
catch (int errorCode) {
 ...code to handle the exception; errorCode is the integer thrown...
}

A catch-clause is only executed if the exception is thrown. If the code inside the try doesn't throw an exception, the catch clause is ignored.

In big programs, there will be many different kinds of possible exceptions, and you probably want to throw more information than just an error code. To do this, create an "exception" class. This can be any class, but it's most common to define subclasses of std::exception or one of its subclasses, such as std::runtime_error, which is defined in the header <stdexcept>. That way, you can handle them specially, if desired, or just catch them along with other exceptions, as shown here.

To process different exceptions differently, use separate catch clauses for each type of exception you want to handle, like this:

try {
 ...code can throw various exception objects...
}
catch (BadURLException &bue) {
 ...code for this case...
}
catch (HostNotFoundException &hnfe) {
 ...code for this case...
}

An Exception No-No

Avoid exception code like this:

try {
  if ( length < 0 || width < 0 ) {
     throw NegativeLengthException();
  }
  ...
}
catch ( NegativeLengthException &nle ) {
  cout << "Negative length found." << endl;
}

There's no point to catching local throw's. That's just an expensive complicated way to do IF, and misses the whole point of exceptions. Demo code does this just to put everything in one small example, but it's not what you really do.

An exception is for cases where some piece of code has to give up. It's up to the code called this code to decide whether there's some alternative or not. If the local function knows what to do, use an IF. For example, use an IF, not an exception, when a function is supposed to load a high-score file, or create one if none exists.

Memory allocation in C++

C has two functions, malloc and free, which dynamically allocate untyped blocks of memory. C++ uses new and delete, which are better for several reasons:

Be sure you know the difference between delete and delete[] and which one to use. Your program will crash otherwise.

Be careful not to try and delete the same memory twice.

Streams

Early programming languages used one set of functions to read and write to the console, and another set of functions to read and write to files. With the stream model, pioneered in Unix and C, there's a single set of functions that read from input streams and write to output streams. You write all your code to use these stream functions. You can then easily "redirect" your program's I/O as needed:

How to read from a stream

There is one fairly simple and reasonably robust way to read in C++, and several more complicated and less robust ways. Here is one not-so-good way:

while ( in.peek() ) {
   in >> x;
   process x
}

Here is another, often seen with file streams:

while ( !in.eof() ) {
   in >> x;
   process x
}

Both of these methods fail to handle input streams with whitespace after the last legal input data. peek() will be true and eof() will be false, but the in >> x; will still fail. And the second method doesn't handle cases where an input stream has bad data and a previous read failed, but end of file has not been seen.

The simpler code that works better is this:

while ( in >> x ) {
   process x
}

This loop will only call process x when data has been successfully read. It works because operator>>() returns the input stream, and the input stream, when tested, acts as if it's true when in a good state and false otherwise. (For a more exact description of how this works, see here.)

istringstreams

An istringstream is an input stream that gets characters from a string. For example,

istringstream in("12 3 456");
int a, b, c;
in >> a >> b >> c;

defines an input stream with three numbers, which can then be read into variables.

An ostringstring is an output stream that sends characters to a string. For example,

ostringstream out;
out << 12 << " " << "abc";

creates an output string stream, and writes 12, space, and "abc" into it. out.str() will return whatever has been written into the output stream.

See Section 18.12 for more examples.

Testing with stringstreams

Stringstreams are especially useful for testing code that reads and writes. It avoids making users enter data and read output, which is basically useless, and it avoids the need for auxiliary text files that are a pain to maintain. For example, if we had a Rational class that represented numbers like 1/2 and 22/7, and it was supposed to support reading and writing values in that form, we could test it like this, using UnitTest++

istringstream in("1/2 22/7");
Rational a, b;
in >> a >> b;
CHECK_EQUAL( Rational(1, 2), a );
CHECK_EQUAL( Rational(22, 7), b );

ostringstream out;
out << a << " " << b;
CHECK_EQUAL( "1/2 22/7", out.str() );

Totally automated. No user action needed. Sweet!

Obey the Rule of Three

If your class dynamically allocates pointers to other structures, you must define a destructor to deallocate them. If you define such a destructor, the Rule of Three says that you must also define a copy constructor and the assignment operator. Otherwise, C++ will use the default definitions that lead to multiple instances with the same internal pointers. When one of those instances is destroyed, which will happen when it's deleted or just goes out of scope, its destructor will delete the memory pointed to and invalidate those pointers. Later, when another instance is destroyed, your program will crash!

Overloading operators

The book gives a fair number of examples of how to overload all the common (and not so common) operators. For a good readable introduction to this topic, see these notes from CalTech.

You need to overload at least == and << if you want to be able to use instances of your classes in code like this:

CHECK_EQUAL( Complex(-1, 0), Complex(0, 1) * Complex(0, 1) );

That's because the above expands into code that uses == to compare the expected and actual values, and << to print them if different.

The CalTech notes implement all operators as member functions. As a general rule, don't do this! It leads to

Instead, where possible, define operators as global functions. Put the operator declaration in the header file, after the class definition, like this:

class Complex
{
  ...
}
 
bool operator<( const Complex &c1, const Complex &c2 );

Put the operator definition in the class implementation (.cpp) file.

Declare operators that need access to private class members as friends at the end of the class definition, like this:

class Complex
{
  ...
 
  friend Complex & operator+=( const Complex &c1, const Complex &c2 );
  ...
}

Declare the operators =, [], (), and ->, if needed, as member functions, because they need to return the class instance, like this:

class Complex
{
  ...
 
  Complex & operator=( const Complex &c );
  ...
}

Note that you only need to define assignment (and a copy constructor) if your class contains pointers to other data structures. Also note that member functions take one less argument. There's an implicit first argument in the this special variable.

Different operators have hidden subtleties to be aware of. If you forget them, you'll get very confusing compiler errors.

The iostream operators

Remember:

The arithmetic operators

If your class is going to support mutation operators like +=, then the general but unintuitive recommendation is to define += first, then use it to define +, not vice versa. This is discussed in the CalTech notes. (Again, ignore the fact that they make the operators member functions.)

There is one small non-obvious refinement you can make for non-member definitions. For example, consider this definition of +:

const MyClass operator+( const MyClass &a, const MyClass &b ) {
  MyClass c( a );
  return c += b;
}

This creates a copy of the first argument, increments it by the second argument, and returns it. But since we need a copy of the first argument, just pass it by value to begin with:

const MyClass operator+( MyClass a, const MyClass &b ) {
  return a += b;
}

The assignment operator

Do you have a destructor? Then the Rule of Three says you need an assignment operator.

Some key rules for writing assignment operators:

The relational operators

You might think that if you overload == and <, then C++ should be able to figure out how to do the other relational operators, like != and <= automatically. It can, sort of, but it doesn't do a very good job. The following in your header file:

#include <utility>
using namespace std::rel_ops;

will define templated versions of the relational operators !=, <=, > and >=, using the obvious code, e.g., (simplified)

template < class T >
bool operator>( const T &x, const T &y ) { return y < x; }

Unfortunately, these templates only match when the two arguments have the same type. Assume your header includes

#include <utility>
using namespace std::rel_ops;

// Code for rational number class
class Rational { 
public:
  Rational( double n = 0, double d = 1 ) : numerator( n ), denominator( d ) { }
  ...
};

bool operator<( const Rational &r1, const Rational &r2);

Then here's what will and won't work in client code:

Rational r( 8, 3 );
bool a = Rational( 2 ) < r; // OK
bool b = 2 < r; // OK, Rational( 2 ) called implicitly
bool c = r > Rational( 2 ); // OK
bool d = r > 2; // WON'T COMPILE, template type mismatch

So, for the current version of C++, using the standard libraries, if mixed comparisons are desired, you need to overload the 4 operators explicitly.

Defining a copy constructor

Do you have a destructor? Then the Rule of Three says you need a copy constructor.

A copy constructor is used whenever you pass or return an instance by value. It is also used when you write code like this:

Rational r(1, 2);
Rational q = r;
Rational s( r );

Since a copy constructor has to copy data from one instance into another, just like the assignment operator, it's tempting to write:

Node::Node( const Node &rhs )
{
  *this = rhs;
}

Many textbooks do this, but it's a bad idea, because assignment has to clear this first, but a copy constructor is starting with an empty object.

For a good example of defining a copy constructor and assignment operator, see CodeGuru. The details are different, since that code just has string arrays, but the principle points are the same.


Comments? comment icon Let me know!

Valid HTML 4.01 Transitional