Department of Engineering

IT Services

C++ course - Nuts and Bolts

Until now we've been avoiding syntactic details, concentrating on concepts. Now we'll try to implement these concepts, revisiting the Standard Library, exceptions and casting before looking at some common errors.

Standard Library

Standardised when ANSI C++ arrived, it includes what was once call "the Standard Template Library" and is useful even for simpler things. It offers new data types (like sets and queues) and routines (like sort and random_shuffle, etc). With it you can avoid writing loops and lots of other code too. However, the notation is a little scary at first. Here for example is the code to create then sort a list of integers

   list<int> x;
   sort(x.begin(),x.end());

Note that it uses templates and objects. If you can cope with this then you can already do quite a lot just by choosing between options
x;
(x.begin(),x.end());

This flexibility isn't at the expense of safety. Behind the scenes, functions still care about whether they're being given the right types of arguments, but the compiler selects the appropriate version for you. Here's another simple example - a complete program this time. It "transforms" a string character by character without explicitly using a loop.

#include <cctype>   // for toupper()
#include <iostream>
#include <string>
#include <algorithm> // for transform()
using namespace std;

char capitalize(char c)
{
  return toupper(c);
}

int main()
{
string text("Mixed Case String");
  cout << "text=" << text << endl;
  transform(text.begin(),text.end(),text.begin(),capitalize);
  cout << "Now text=" << text << endl;
  return 0;
}

or in C++11 using a lambda function

#include <cctype>   // for toupper()
#include <iostream>
#include <string>
#include <algorithm> // for transform()
using namespace std;
int main()
{
string text("Mixed Case String");
  cout << "text=" << text << endl;
  transform(text.begin(),text.end(),text.begin(),[] (char c) { return toupper(c);});
  cout << "Now text=" << text << endl;
  return 0;
}

The library has support for complex numbers, vectors, lists, etc. vectors are a replacement for arrays. Here's an example showing how you can use them like arrays, and also do a lot more - the vector can expand, you can find out its size, and you can use more standard algorithms like reverse on it

#include <iostream>
#include <vector> // needed for vector
#include <algorithm> // needed for reverse 

using namespace std; 
int main() { 
   vector<int> v(3); // Define v to be a vector of 3 ints 
   v[0] = 7; 
   v[1] = v[0] + 3; 
   v[2] = v[0] + v[1]; 
   v.push_back(99); // make the vector one element bigger.
 
   for (int i=0;i<v.size();i++)
      cout << v[i] << endl;

   reverse(v.begin(), v.end()); 

   for (int i=0;i<v.size();i++)
      cout << v[i] << endl;
}

The following code removes spaces in a string (a task asked about in C++ interviews). Note that the remove command doesn't remove elements, it just shunts them to the end of the string, returning a pointer to the first of the unwanted elements. In this example, that pointer is given to erase, which removes the unwanted elements

#include <iostream> 
#include <string> 
#include <algorithm> 
using namespace std; 
int main() { 
   string s="spaces in text"; 
   cout << s << endl; 
   s.erase(remove(s.begin(), s.end(), ' ' ), s.end() ) ; 
   cout << s << endl; 
}

The library also has a function that conditionally copies items elsewhere. It's called remove_copy_if

#include <vector> 
#include <algorithm> 
#include <iostream> 
#include <iterator> 
using namespace std; 

int main() { 
   vector<int> V; 
   V.push_back(0); 
   V.push_back(1); 
   V.push_back(2); 
   vector<int> V2; 
   //copy the elements from V that are not <1 into V2 
   remove_copy_if(V.begin(), V.end(), back_inserter(V2), bind2nd(less<int>(), 1)); 
}

The following code uses a 'stack' of complex numbers with integer components.

#include <deque>
#include <stack>
#include <complex>
#include <iostream>
using namespace std;

int main() {
  stack< complex<int> > S; // the space between the '>' signs matters in pre-C++11
  complex<int> a(1,2), b(4,7);
  S.push(a);
  S.push(b);
  S.push(a);
  S.pop();
  cout << "Top element is " <<  S.top()  <<endl;

  S.pop();
  S.pop();
  cout << "Number of elements is now " << S.size() <<endl;
  
  cout << "Stack empty? (1 means yes) " << S.empty() <<endl;
};

Some of this may look unfriendly at first. The good news is that once you can "transform" a string of characters, the orthogonal design makes it easy for you to transform a vector of doubles, or sort it, search it and permutate it. If you don't want a stack, you can use a queue instead by changing a line or 2. So the time you invest is soon recouped. For examples see

Standard Library Quiz

Look at the following code (the line numbers aren't part of the code)

#include <deque>                  [0]
#include <algorithm>              [1]
using namespace std;              [2]
int main()                        [3]
     {                            [4]
     deque<int> Q;                [5]
     Q.push_back(3);              [6]
     Q.push_front(1);             [7]
     Q.insert(Q.begin() + 1, 2);  [8]
     sort(Q.begin(), Q.end());    [9]
     Q[3] = 0;                   [10]
     }                           [11]
  1. What is Q? a stack a double-ended queue a delimited queue
  2. Does the code compile? Yes No
  3. Where is the bug?
    line 6 - you can't add new items in this way unless you create space for them first
    line 9 - you're not allowed to sort something of this type
    line 10 - you can't add new items in this way unless you create space for them first
  4. The following line attempts to create a stack of complex numbers with components that are floats.
    stack<complex<float>> S;
    
    However, the line doesn't compile. Why?
    Because you can't have floats in stacks
    Because ">>" means "shift bits right"
    Because you can't create an empty stack
  5. Look at the following code (the line numbers aren't part of the code)
    #include <vector>                                             [0]
    #include <algorithm>                                          [1]
    using namespace std;                                          [2]
    int main()                                                    [3]
    {                                                             [4]
    // Create a vector of integers                                [5]
    int N[5] = { 1, 2, -3, -4, 7 };                               [6]
    vector<int> v(N, N + 5);                                      [7]
    // Remove negative numbers from the vector                    [8]
    remove_if(v.begin(), v.end(), bind2nd(less<int>(), 0));       [9]
    // Print a sequence of non-negative numbers                  [10]
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, "\n")); [11]
    }                                                            [12]
    
    The code is supposed to print out the 3 positive numbers. However, on my machine it prints out 1, 2, 7, -4, 7. What's wrong?
    remove_if doesn't remove elements
    Using copy overwrites elements
    v.finish() should be used rather than v.end()

Exceptions

When an exception is 'thrown' it will be 'caught' by the local "catch" clause if an appropriate one exists, otherwise it will be passed up through the call hierarchy until a suitable "catch" clause is found. The default response to an exception is to terminate.

Here's an example. Note that main doesn't call function g directly but can still catch exceptions that g throws.

#include <iostream>
using namespace std;

void g()
{
  throw 42;
}

void f() {
  g();
}

int main ()
{
  try {
     f();
  }
  catch (int codenumber) {
     cout << "Caught error " << codenumber << endl;
  }
}

There are many types of standard exception - they form a class hierarchy of their own.

try {
// code
}
// catch a standard exception
catch(std::exception& e){
 // order of the catch clauses matters; they're tried 
 // in the given order
}
// catch all the other types
catch(...) {
// cleanup
 throw;   // throw the exception up the hierarchy
}

The following shows how to invent your own exceptions.

#include <iostream>
#include <string>
using namespace std;


class DivZero {
public:
  DivZero() { message="Division by Zero!";} 
  void print() const { cout << message << endl;}
private:
  string message;
};

int divide (int top, int bottom)
{
  if (bottom == 0)
    throw DivZero();
  return top/bottom;
}

int main(int argc, char* argv[])
{
  try 
  {
    cout << divide (3,2) << endl;
    cout << divide (3,0) << endl;
  }
  catch (DivZero error)
  {
      error.print();
  }
}

This code catches divide-by-zero errors. As an exercise you could create another exception type and add another catch clause to treat as errors integer divisions that have non-zero remainders.

Casting

Some languages let you mix variables of different types or use one variable to store different types of things. For example in some some languages something like the following sequence is possible

x=1;
x="one";
x=x+1
x=x*2;

The result might be "one1one1". C++ is much fussier. Sometimes you need explicitly to convert a variable from one type to another. For example,

int seven=7,two=2;
float answer;
 answer=seven/two;

gives an answer of 3 rather than the perhaps intended 3.5, because when 2 ints are combined, the answer is always an int. If one of the ints can - just for the purposes of the calculation - be converted to a float, the answer will be 3.5.

C++ is strict about converting from one type to another. When the 2 types are closely related (int and float for example), casting's easy. Parent and child objects are also closely related. Even then you still have to be careful - see the C++ and simple type conversion page for worrying examples. Note that C had a way of forcing conversion, but it's not recommended in C++. Try to use the least dangerous (most specific) casting alternative from those below

  • const_cast - used to remove the const qualifier.
  • dynamic_cast - does careful, type-sensitive casting at run time. Returns 0 (or throws in the case of exceptions) when the conversion is not safe.
  • reinterpret_cast - produces something that has the same bit pattern as the original.
  • static_cast - doesn't examine the object it casts from at run time. This is fast if it's safe and - the compiler should tell you if it isn't safe. It lets you force a conversion that is already defined, or do a conversion from A to B where B to A is already defined. Can deal with void*.

So to fix our example we could use

int seven=7, two=2;
float answer;
 answer=seven/static_cast<float>(two);

All of the casting operators have the same syntax and are used in a manner similar to templates.

Casting Quiz

Look at the following code (the line numbers aren't part of the code)

#include <iostream>               [0]
using namespace std;              [1]
int main()                        [2]
{                                 [3]
 const int c = 28;                [4]
 const int* a;                    [5]
 a = &c;                          [6]
 cout<<"a points to "<<*a<<endl;  [7]
 int* b = const_cast<int*>(a);    [8]
 *b = 23;                         [9]
 cout<<"b points to "<<*b<<endl; [10]
 cout<<"c="<<c<<endl;            [11]
 cout<<"a points to "<<*a<<endl; [12]
 return 0;                       [13]
}                                [14]
  1. Why does line 8 use a cast?
    Because it's creating a pointer
    Because a has already been given a value
    Because you can't assign a const int* to a int* unless you "cast away" the const
  2. Run the code. Pointer a points to c (because of line 6) and pointer b points to the same place (because of line 8). And yet, if you run the code, pointers a and b might well seem to be pointing to different places. What's going on?
    You should never have 2 pointers pointing to the same place.
    After line 8, behaviour is undefined
    You shouldn't cast pointers.

Common Problems

By now so much notation has been shown to you that you may be getting worried about making programs compile, let alone work. C++ has all the problems that C has, plus many others. Here's a selection -

C errors
Look at the Find the bug section of the C tutorial.
Legal errors
- Some syntactic mistakes still produce legal code. For example,
  • cout < "Print this!"; prints nothing - the "<" should be "<<"
  • if (i=0) cout << "Print this!"; prints nothing - the "=" should be "=="
  • if (1<i<5) cout << "Print this!"; doesn't do what mathematicians might expect. The condition should be "(1<i and i<5)"

Obscure Error Messages
- small errors in a simple-looking line can produce complicated messages like the following

Error 226: "set1.cc", line 16 # No appropriate function found for call of
    'operator <<'. Last viable candidate was "ostream &ostream::operator
    <<(char)" ["/opt/aCC/include/iostream/iostream.h", line 509]. Argument of
    type 'class set<int,less<int>,allocator>' could not be converted to
    'char'.
     cout << "s1 = " << s1;
     ^^^^^^^^^^^^^^^^^^^^^

The ^^^^ symbols show which part of the line is at fault. In this case the compiler can't narrow down the problem, so next read the first (easiest) part of the error message. There's something wrong with the use of <<. Remember that in C++ many functions can be invoked by an operator - it all depends on the number and type of operands. In this case s1 (a set) isn't something that there's a matching function for - hence the error message. The compiler, trying to be helpful, gives the next best option - a function tied to << that can deal with a char.

Another example: the line double d = sqrt(4); produces the following error because the integer given to sqrt has to be promoted to a real, but the compiler doesn't know what kind of real.

Error 225: "foo.cc", line 6 # Ambiguous overloaded function call; more than
    one acceptable function found. Two such functions that matched were "long
    double sqrt(long double)" ["/opt/aCC/include/cmath", line 107] and "float
    sqrt(float)" ["/opt/aCC/include/cmath", line 57].

Obscure declarations and definitions
- C++ has fewer keywords than many languages but it makes up for this by having many meanings attached to a symbol. For example '<' is used in #include lines, cout, in template definitions as well as meaning 'less-than'. This makes deciphering declarations hard. c++decl can sometimes help. For example, on the Teaching System
   c++decl explain "int* &cp"
prints "declare cp as reference to pointer to int".

Object creation
- The first 2 lines below create a string variable s. The 3rd doesn't create an empty string - it declares a function s that returns a string.
String s;
String s("initial");
String s();
Order of operations
If you run the following code, what will be printed out?
#include <iostream>
using namespace std;

class Test {
  int i;
public:
  Test() {i=0;};
  int add1toi() { i=i+1; return i; }
  int add2toi() { i=i+2; return i; }
};

int main() {
  Test t1;
  cout << t1.add1toi();
  cout << t1.add2toi();
  cout << endl;
  // do the same again, all on the same cout line
  Test t2; 
  cout << t2.add1toi() <<  t2.add2toi() << endl;
}
On my machine this prints out
13
32
Why? With t1, add1toi() is run then the result printed. Then add2toi() is run and the result printed. i is initialized to 0, so unsurprizingly 13 appears. With t2, add2toi() is run first, then add1toi() is run, then the result of add1toi() is printed, then the result of add2toi() is printed. Even though the result of add1toi() has to be printed before result of add2toi(), that doesn't mean that add1toi() has to be run before add2toi(). With t1 the semicolon at the end of the first cout line forced the issue - it's a "sequence point".
References
The following code does something rather strange
#include <iostream>
using namespace std;
int main ()
{
  int i = 1;
  int& ref1 = i;
  const unsigned& ref2 = i;

  i = 2;
  cout << "i=" << i << ", ref1=" << ref1 << ", and ref2=" << ref2 << endl; 
}
Note that it compiles ok, mostly likely without a warning. Before running it, guess what it will print out. ref1 behaves as expected - the types match - but ref2 is more problematic. If ref2 is going to be an alias for i it needs to be the same type. Without the const it wouldn't compile, but here the compiler creates a temporary "unsigned int" object with the current value of i and makes ref2 an alias for it, so subsequent changes to i will only affect ref1, not ref2 (which after all is supposed to be a constant). I find that confusing.

Debugging

See the debugging handout or the CERT C++ Secure Coding Standard

Redefining operators

C++ is a powerful language, but once you start using some of the more powerful features you come up against more subtle problems. To illustrate these, let's look at an example of a class definition.