Classic, old-fashioned C++ developers use print statements to debug code. However, debugging with print tracing messages is a time-consuming task, as you need to put quite a lot of effort into writing print statements at multiples places, recompiling, and executing the application.
The old-style debugging approach requires many such iterations and, typically, every iteration requires adding more print statements in order to narrow down the issue. Once the issues are fixed, we need to clean up the code and remove the print statements, as too many print statements tend to slow down application performance. Also, the debug print messages will distract and are irrelevant for the end customers using your product the production environment.
The C++ debug assert() macro statement that comes with the <cassert> header can be used for debugging. The C++ assert() macros can be disabled in release mode and are only enabled in debug mode.
Debug tools are there to rescue you from such tedious efforts. The GDB debugger is an open source CLI tool, which is the debugger for C++ in the Unix/Linux world. For Windows platforms, Visual Studio is the most popular one-stop IDE with inbuilt debugging facilities.
Let's take a simple example:
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
using namespace std; //Use this judiciously - this is applicable throughout the book
class MyInteger {
private:
int number;
public:
MyInteger( int value ) {
this->number = value;
}
MyInteger(const MyInteger & rhsObject ) {
this->number = rhsObject.number;
}
MyInteger& operator = (const MyInteger & rhsObject ) {
if ( this != &rhsObject )
this->number = rhsObject.number;
return *this;
}
bool operator < (const MyInteger &rhsObject) {
return this->number > rhsObject.number;
}
bool operator > (const MyInteger &rhsObject) {
return this->number > rhsObject.number;
}
friend ostream & operator << ( ostream &output, const MyInteger &object );
};
ostream & operator << (ostream &o, const MyInteger& object) {
o << object.number;
}
int main ( ) {
vector<MyInteger> v = { 10, 100, 40, 20, 80, 70, 50, 30, 60, 90 };
cout << "\nVectors entries before sorting are ..." << endl;
copy ( v.begin(), v.end() , ostream_iterator<MyInteger>( cout, "\t" ) );
cout << endl;
sort ( v.begin(), v.end() );
cout << "\nVectors entries after sorting are ..." << endl;
copy ( v.begin(), v.end() , ostream_iterator<MyInteger>( cout, "\t" ) );
cout << endl;
return 0;
}
The output of the program is as follows:
Vectors entries before sorting are ...
10 100 40 20 80 70 50 30 60 90
Vectors entries after sorting are ...
100 90 80 70 60 50 40 30 20 10
However, our expected output is the following:
Vectors entries before sorting are ...
10 100 40 20 80 70 50 30 60 90
Vectors entries after sorting are ...
10 20 30 40 50 60 70 80 90 100
The bug is obvious; let's go easy with GDB learning. Let's first compile the program in debug mode, that is, with the debugging metadata and symbol table enabled, as shown here:
g++ main.cpp -std=c++17 -g