We will first start off with a small test to check your inheritance skills after session 4. Afterwards, "safe programming" is the main topic of today's lab session: error handling through the use of exceptions. We'll also cover a small addition to operator overloading: user-defined typecast operators. Also covered: a utility library for parsing command-line arguments and some interesting debugging tools to help improve your (project) C++ code.
Look at following source code files. Try to figure out what is going on and what will be printed. Check your answer by running the actual code.
Test 1:
#include <iostream>
class Base {
public:
void print() { std::cout << "Base!" << std::endl; }
};
class Derived : public Base {
public:
void print() { std::cout << "Derived!" << std::endl; }
};
int main()
{
Base b;
Derived d;
b = d;
b.print();
Base* b2 = new Derived();
b2->print();
}
Test 2:
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor!" << std::endl; }
Base(int b) : x(b) { std::cout << "One argument Base constructor!" << std::endl; }
virtual void print() { std::cout << "Base!" << std::endl; }
private:
int x;
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor!" << std::endl; }
Derived(int d) : x(d) { std::cout << "One argument Derived constructor!" << std::endl; }
virtual void print() { std::cout << "Derived!" << std::endl; }
private:
int x;
};
int main()
{
Base* b2 = new Derived(5);
b2->print();
}
Test 3:
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor!" << std::endl; }
virtual ~Base() { std::cout << "Base destructor!" << std::endl; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor!" << std::endl; }
~Derived() { std::cout << "Derived destructor!" << std::endl; }
};
int main()
{
Base* b = new Derived();
delete b;
Derived d2;
Base b2(d2);
}
Be sure that you really understand what is going with each test. If you have questions concerning the output, do not hesitate to ask!
Important C++ has a well-structured mechanism to report and handle errors and unforeseen events: exceptions. (unforeseen or exceptional in this context means: "some part of the system couldn't do what it was asked to do")
An exception is an object of some class representing an exceptional event. It might (and often does) contain additional information about the cause of the event; for instance a string containing a more detailed error message. Since exceptions are instances of classes, they can be grouped in hierarchies of exceptions. This allows for a hierarchical error handling: specific errors (think: derived classes) can be handled closer to where the error occurred; for instance deep in the internals of some library.
More general errors (think: base classes) can be handled higher up in the hierarchy; for instance in client code that uses the library.
The code that could throw an exception is wrapped in a try
block.
Whenever an exception is thrown, the program's control flow immediately jumps to the catch
blocks that directly follow the try
block.
The catch
blocks are considered in order; the one that first matches the thrown exception's type is executed.
After handling the exception in the catch
block, the program resumes "normal" execution right after the catch blocks.
Play around with the example below to see how it works:
try {
// Code that might throw an exception, for example:
throw MyException();
} catch (MyException& e) {
// Handle a 'MyException'
cout << "MyException: don't worry, be happy!" << endl;
} catch (YourException& e) {
// Handle a 'YourException'
cout << "YourException: this really is the end of it all!" << endl;
} catch (...) {
// catch-all: handle every other exception that did not match
// any of the above catch clauses
cout << "Something happened but I don't know what..." << endl;
}
cout << "Just kidding, carry on!" << endl;
The semantics of catching an exception is the same as a function accepting an argument: initialization.
If you want to prevent slicing, you'll have to catch exceptions by pointer or by reference, as is done in the example above.
You can also use const
to prevent accidental changes to exceptions in handlers.
Obviously try
-catch
blocks can be nested; for example:
try {
// stuff to try
try {
// other stuff to try
} catch (std::exception &e) {
// solve problem
} catch (...) {
// solve another problem
}
} catch (...) {
// whatever
}
Watch out you don't end up reproducing if
-then
-else
blocks with a mess of nested or linear exception handlers: read C++FAQ on exceptions.
A previously thrown exception can be re-thrown for further error handling:
try {
// whatever
} catch (std::exception &e) {
// handle the error partially
throw;
}
Note that the original exception is re-thrown! (I.e.: no slicing etc...)
As you might have guessed, special care needs to be taken when exceptions are used during an object's construction and destruction. When an exception is thrown in a constructor, the object is not fully constructed. The destructor (that might otherwise free the previously allocated resources) of the "half-baked" object will not be called, which leaves the object in a potentially half-initialized state with dangling resources. A good rule of thumb is that every member object must manage its own destruction. This is discussed in the C++ FAQ's section on selfcleaning members.
I highly recommend you to read cppreference's RAII: Resource Acquisition Is Initialization programming idiom now. This idiom also goes by the more intuitive name "Scope-Bound Resource Management".
Constructors (don't forget the copy constructor!) allow a way of enclosing their initializer lists in a try
block as well.
Consider this hypothetical example:
Sprite.h
:
/**
The Sprite class represents an image that can be drawn on the screen.
*/
class Sprite {
public:
Sprite(std::string file_name);
~Sprite();
void setImage(Image img);
void draw(Surface s, Coord2d xy);
private:
Image m_img;
};
Sprite.cpp
:
#include "Sprite.h"
Sprite::Sprite(std::string file_name)
try : m_img(file_name) {
// constructor
} catch (exception& e) {
cout << "Error loading sprite image: " << e.what() << << endl;
}
Sprite::~Sprite() {
// We don't need to clean up anything. Image's destructor will releaase
// its own resources. Cfr.: RAII.
}
void Sprite::setImage(Image img) {
m_img = img;
}
void Sprite::draw(Surface s, Coord2d xy) {
// Draw sprite's image on the surface s at position xy
...
}
Whenever a Sprite
is being constructed requesting an image file that cannot be accessed for whatever reason, Images
's constructor throws an exception that will be caught by Sprite
's constructor.
The situation can be resolved later by loading the proper image manually with Sprite::setImage
.
As a general rule, destructors must not throw exceptions!. More specifically, if a destructor throws an exception, it must be handled internally within the destructor. It may not leave the destructor's body! See: C++ FAQ 17.9.
Of course, you can try this just for fun:
class A {
public:
~A() {
throw 1;
}
};
int main() {
try {
A a;
throw 1;
} catch (...) {
cout << "Will you print me, dear C++?" << endl;
}
return 0;
}
Notice I'm throwing an int
variable, which is perfectly fine in C++.
You can throw whatever you want, as long as it can be copied.
This doesn't mean that you should do this though.
Generally it is considered best-practise to only throw exceptions that are derived from std::exception.
Except in very special cases (have a look at the last paragraph of in this C++ FAQ thread) the memory allocated by the new
operator will be freed properly when the constructor of the class whose object is being allocated throws an exception.
For example, in cases like:
Foo* f = new Foo;
or
Foo* f = new Foo[10];
The correct use of exceptions is a difficult subject and attracts a lot of extreme opinions. Read some articles to broaden your perspective. People even dedicate whole lectures to exception safe coding in C++. Have a look; you'll probably learn something new.
The C++'s standard library has a wide variety of different exceptions to handle all the possible error scenarios.
See here for an overview.
Notice that all exceptions in the standard library are derived from the std::exception
base class (#include <exception>
to make it work).
The std::exception
class has a (virtual) what()
method to retrieve a string with more information about the error.
In the case of the base class this error message is an empty string.
More specific error messages are provided by subclasses of std::exception
.
Try this:
try {
vector<int> v = {2, 3, 5, 7};
v.at(4) = 11;
} catch (exception& e) {
cout << e.what() << endl;
}
Notice v.at(4)
is used instead of the more familiar v[4]
.
The difference is that at()
does range checking and throws an std::out_of_range
exception if you're trying to access elements outside the container's range.
On the other hand, []
does no range checking whatsoever and possibly results in a less gracious "Segmentation fault" in the above example.
Of course, you can derive your own exceptions using std::exception
as a base class.
If you just want a simple exception class with a string as a "reason" and no custom hierarchy of errors you can use std::runtime_error
(You'll need to #include <stdexcept>
).
It's really very convenient!
try {
...
throw runtime_error("Computer says NO!");
...
} catch (runtime_error& e) {
cout << "An error occurred: " << e.what() << endl;
}
Look at cppreference.com for a short explanation about which exception fits which situation: save yourself some work and check this list first before implementing your own exception class!
Beware that deriving directly from std::exception
is perfectly fine, but has the disadvantage that you do not have a constructor that accepts a std::string
which is returned by the what()
call.
Plus, you should also check the list to see if there is a more specific exception from which you should derive.
There's a useful set of operators that we haven't covered yet which can be overloaded as well: the type-cast operators.
Recall the Integer
wrapper exercise / example from session 2. There is, for instance, no way of doing:
Integer a = 4;
int b = a;
You'll get:
error: cannot convert 'Integer' to 'int' in initialization
To make the above statement possible you need to overload the type-cast operator Integer::operator int()
.
The proper way of doing this is:
Integer.h
:
class Integer {
...
operator int() const;
...
};
Integer.cpp
:
Integer::operator int() const {
return this->m_val;
}
In some cases it's extremely useful to provide your program with command line arguments.
That's where the mysterious int argc, char* argv[]
arguments of the main
function are for: argc
contains the number of (whitespace-separated) arguments passed (argument count), argv
is an array of null terminated strings that contains these arguments (argument values).
Note that main
is a very special function, being the only one that has optional parameters and return value (the compiler will return 0 if nothing is specified), so you might not always encounter it in this form.
Try this:
#include <iostream>
int main(int argc, char* argv[]) {
for (int i = 0; i < argc; ++i) {
std::cout << argv[i] << std::endl;
}
return 0;
}
Compile & run:
g++ -o arguments main.cpp
./arguments --foo "a" bar -baz=3
You should see:
./arguments
--foo
a
bar
-baz=3
Note that the name of the program is an argument as well! This can be used in nifty ways. An example of this is BusyBox, a collection of small versions of the most common UNIX utilities bundled in one executable. Depending on the link that's used to call the executable, different routines will be called. This saves a lot of disk space in systems where storage is limited and reduces the need for libraries!
If you've got time to waste you can start writing your own parser that can deal with all kinds of possible situations to interpret the arguments in argv
.
There are quicker ways to deal with that issue though.
TCLAP stands for "Templatized C++ Command Line Parser". It's a small, useful header-only library to handle command line arguments elegantly. Try this short example:
#include <iostream>
#include <tclap/CmdLine.h>
using namespace std;
int main(int argc, char* argv[]) {
try {
// Object that manages the command line parsing
TCLAP::CmdLine cmd("My program", ' ', "0.1");
// Define the arguments
TCLAP::ValueArg<int> n_arg("n", "num_whatever",
"Number of whatevers I need for whatever else",
false, 0, "integer");
TCLAP::ValueArg<string> input_arg("i", "in", "Input file name", true,
"", "file path");
TCLAP::SwitchArg verbose_arg("v", "verbose", "Verbose mode");
// Add arguments to the parser
cmd.add(n_arg);
cmd.add(input_arg);
cmd.add(verbose_arg);
// Parse arguments
cmd.parse(argc, argv);
// Use the parsed arguments
cout << "n = " << n_arg.getValue() << endl;
cout << "i = " << input_arg.getValue() << endl;
cout << "v = " << verbose_arg.getValue() << endl;
} catch (TCLAP::ArgException &e) {
cout << "error: " << e.error() << " for arg " << e.argId() << endl;
}
return 0;
}
If you run the program with the --help argument (or forget to use the correct arguments), you'll see that automatically you also have a nice help page!
Have a look at the TCLAP manual or these TCLAP slides for more details about its usage.
It's really convenient!
You can install it on Ubuntu using sudo apt install libtclap-dev
, or directly download the latest source.
boost::program_options
Another alternative is to use argument parsing facilities from boost: Boost.Program_options. Have a look at the documentation for more details. There's a tutorial as well.
Important Two popular tools to improve your C++ programs are GDB and Valgrind. They assist you in, among other things, debugging C++ code and check for memory leaks (in say, a game programming project).
Try to debug following code using GDB and fix it:
#include <iostream>
using namespace std;
long factorial(int n) {
long result(1);
while (n--) {
result *= n;
}
return result;
}
int main() {
int n(0);
cin >> n;
long val = factorial(n);
cout << val;
return 0;
}
Is something wrong with this piece of code? Find out with Valgrind!
#include <iostream>
#include <vector>
using namespace std;
class Foo {
public:
Foo(int instance) : m_instance(instance) {};
~Foo() {};
void print() { cout << "Foo instance " << m_instance << endl; }
private:
int m_instance;
};
int main() {
vector<Foo*> fooVec (10);
for (int i = 0; i < 10; i++) {
fooVec[i] = new Foo(i);
}
for (int i = 9; i >= 0; i--) {
fooVec[i]->print();
}
fooVec.erase (fooVec.begin(), fooVec.end());
return 0;
}
Go to the assignment: https://classroom.github.com/a/9vZDabfq
Go to the assignment: https://classroom.github.com/a/xtwkowhc
Go to the assignment: https://classroom.github.com/a/ISxOWwJf