Modern C++: Quick Reference

Even though I live and breath C++, until recently I was working with older version of C++. Working with old compilers on in-house framework leaves little space to learn, practice & enjoy the merits of newer versions of C++ at work. Sometime back (in 2018), I was involved in migrating a C++ 98 application on Solaris to C++ 11 on Linux.
This page came to existence from my personal notes on some core features of C++ 11. Sometime there are somethings that don’t have anything to do with C++ 11, they may be just something important to me.
I will keep on updating as and when I will find some interesting stuff on C++.

1. “auto” Type Specifier

auto item = variable1 + variable2;

item’s type is deduced from the type of the result coming out of the addition of variable1 & variable2

auto i = 0, *p = &i; // OK, as data type for both is same (int)
auto sz = 0, pi = 3.14; 
// ERROR, as sz and pi can have different types
int i = 0, &ref = i;
auto a = ref; // OK, a is an int
const int ci = i, &cr = ci;
auto b = ci; // b is int (top-level const dropped)
auto c = cr; // c is int (cr is ci, so same as before)
auto d = &i; // d is int* (as we passed an address)
auto e = &ci; // e is const int*
// (addr. of const obj is low-level const)
const auto f = ci; // f is const int, explicitly
// requested to apply top-level const
auto &g = ci; // g is const int &
auto &h = 42; // ERROR, cannot make non-const
// reference to literal
const auto &h = 42; // OK, const reference to literal
auto k = ci, &l = i; // k is int and l is int &
auto &m = ci, *p = &ci; // m is const int &
// p is const int *
auto &n = i, *p2 = &ci; // ERROR, n is int and p2 is const int*
// types are different

2. “decltype” Type Specifier

This defines a variable with a type that the compiler deduces from an expression but do not want to use the expression to initialize the variable.

decltype(fn()) variable = x; // variable is of type 
// returned by fun()
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x is const int
decltype(cj) y = x; // y is const int &
decltype(cj) z; // ERROR, z gets defiend as const int&
// without any initialization
int i = 15, *ptr = &i,  ref& = i;
decltype(ref + 0) b; // OK, r + 0 evaluates to int
decltype(*ptr) c; // ERROR, c is int & and must be initialized

Note: as ptr is a pointer, *ptr gets the object it is pointing to. Moreover, we can assign to the object. Thus the type deduced is int & and not int.

decltype((i)) d; // ERROR, d is int&
decltype(i) e; // OK, e is int

NOTE: decltype((variable)) always creates reference. Note the double braces around the variable.

3. cout evaluation

cout << "ABC" << endl; // gets evaluated as shown below
( cout << "ABC" ) << endl;

Therefore,

int i = 1;
cout << i << i++ << ++i << --i << i++ << i; // output: 113223
1 1 3 2 2 3
Simply the evaluation is from left to right due to above logic

4. Preprocessor to define header guards

While creating a header file use header guards.

#ifndef CLASSNAME_H  <- Was CLASSNAME_H was defined earlier
#define CLASSNAME_H <- if not, define it now and rest
// :
// :
#endif <- end of if

5. Headers should not include “using” Declarations

Codes inside header ordinarily should not use “using” declaration. This is because the content of the header gets into the including programs text. If a header has a “using” declaration, then every program that includes the header gets the same “using” declaration. As a result, a program that did not intend to use the specified library name might encounter unexpected name conflicts.

6. Range-Based for / Range for

Syntax:

for ( declaration : expresssion )
statement;
string str("random string");
for ( auto c : str )
cout << c;
cout << endl;
----------------------------
random string
string str("random string");
for ( auto &c : str )
c = toupper(c);
cout << srt << endl;
----------------------------
RANDOM STRING

7. List initialize

In action:

vector<string> fewWords = {"This", "is", "very nice" };
// or
vector<string> fewWordsAgain{"This", "is", "very nice" };

After auto, this is my next favorite feature of C++ 11. This is used to create functions with varying arguments. If all arguments have the same type we can use initializer_list

void error_message(initializer_list<string> il)
{
for ( auto begin = il.begin(); begin != il.end(); ++begin)
cout << *beg << " ";
cout << endl;
}

int main()
{
error_message({"This", "is", "a test error message"});
}

8. Vector of Vectors

vector<vector<int> > vvi; // OK, in old compilers the space
                          // between last two closing angular 
                          // brace is required "> >"
// C++ 11 onwards, its not required any more
vector<vector<int>> vvi2; // OK

9. nullptr

In stead of using NULL or 0 to denote null pointers, in C++ 11 one can use nullptr. “nullptr” is a keyword. It can be used at all places where NULL is expected. Unlike NULL, it is not implicitly convertible or comparable to integral.

10. Function Template

template <typename T>
int compare(const T &left, const T &right)
{
if (left < right) return -1;
if (left > right) return 1;
return 0;
}
template <typename T>
T foo(T *ptr)
{
T temp = *ptr;
// ...
return temp;
}

There is no distinction between typename and class

template <typename T, class U>
T compute( const T*, const U*);

11. Class Template

// This is in Blob.h file
template <typename T>
class Blob {
public:
Blob();
Blob(const T&);
// ...
private:
std::shared_ptr<std::vector<T> > data;
};
// This is in Blob.cpp file
template <typename T>
return-type Blob<T>::memner-function(parameter-list)
{
// ...
}

12. Run-time type identification (RTTI)

dynamic_cast <type *>(e)
dynamic_cast <type &>(e) // Single & is for lvalue reference
dynamic_cast <type &&>(e) // Double && is for rvalue reference

If a dynamic_cast to a pointer type fails, the result is 0.

If a dynamic case to a reference type fails, the operator throws an exception of type bad_cast

void fn(const Base &base) {
try {
const Derived &derived = dynamic_cast<const Derived &>(base);
} catch(bad_cast) {
// ...
}
}

13. Problems with auto_ptr

auto_ptr<Base> ptr(new Base());
auto_ptr<Base> ptr2 = ptr; // This is bad, it let us passed the
// control to another auto_ptr and
// both are in control
// auto_ptr cannot be stored in containetrs
unique_ptr<Base> ptr(new Base());
unique_ptr<Base> ptr2 = std::move(ptr);
// Passing of ownership needs to happen explicitely
// unique_ptr can be used in containers

14. rvalue reference

We refer to a traditional C++ reference as an lvalue reference. An lvalue reference is formed by placing an & after some type.

 A a;
A& a_lref1 = a; // an lvalue reference
const A& a_lref2 = A(); // an lvalue reference

An rvalue reference is formed by placing an && after some type.

 A a; 
A&& a_rref1 = A(); // an rvalue reference

An rvalue reference behaves just like an lvalue reference except that it can bind to a temporary (an rvalue), whereas you can not bind a (non const) lvalue reference to an rvalue.

A&  a_lref3 = A();  // Error! 
A&& a_rref4 = A(); // Ok

When I say “temporary” it means that the right hand side object is going to lose it’s existence.

 int i = 4;
 int &lval = i; // Variable i is not temporary, so lvalue reference
 int &&rval = i + 4; // the result of i + 4 is stored temporarily
                     // so rvalue reference is used. 

15. Move Constructor

They work similar to the corresponding copy operations, but they “steal” resources from their given object rather than copy them. Suppose object A stores pointers, a copy constructor copying data from A would need to make a duplicate of what A’s pointer is pointing to and then store the pointer to the duplicate. However, if we know that A is going to be destroyed and can never be used, why create a duplicate while copying from A. In stead one can take the pointer from A and place something else in A’s pointer such that when A is being destroyed, the destructor without an error. Thus, we stole resource from dying A.

 class HasPtr
 {
 public:
    // Default constructor
    HasPtr() { ptr = nullptr; cout << "HasPtr()" << endl; }
    //Copy constructor
    HasPtr(const HasPtr& rhs) { 
       ptr = new string(*(rhs.ptr)); 
       cout << "HasPtr(HasPtr&)" << endl; 
    }
    //Custom constructor
    HasPtr(string* pVal):ptr(pVal) { cout << "HasPtr(string*)" << endl; }
    // Move constructor
    HasPtr(HasPtr&& rhs) noexcept { 
       ptr = rhs.ptr; 
       rhs.ptr = nullptr; 
       cout << "HasPtr(HasPtr&&)" << endl; 
    }

    // Assignment operator
    HasPtr& operator=(HasPtr& rhs) {
       if(ptr != nullptr) delete ptr;
       ptr = new string(*(rhs.ptr));
       cout << "operator=(HasPtr&)" << endl;
       return (*this);
    }
    // Move Assignment operator
    HasPtr& operator=(HasPtr&& rhs) {
       if(ptr != nullptr) delete ptr;
       ptr = rhs.ptr;
       rhs.ptr = nullptr;
       cout << "operator=(HasPtr&&)" << endl;
       return (*this);
    }  

    // Destructor
    ~HasPtr() { 
        if(ptr != nullptr) delete ptr; 
        cout << "~HasPtr()" << endl;
    }

    void set(string* p) { if (ptr != nullptr) delete ptr; ptr=p; }
 private:
    string* ptr;
 };
 
 // A function returning HasPtr
 HasPtr foo(string s) {
     return HasPtr(new string(s));
 }



int main() {
     int i = 4;
     int &r = i;
     i++;
     int &&rval = i + 4;
 
     cout << "i = " << i << endl;
     cout << "r = " << r << endl;
     cout << "rval = " << ++rval << endl; 

     HasPtr a; a.set(new string("1st Elem"));
     HasPtr b(a);
     HasPtr c = b;
     HasPtr d(new string("2nd Elem"));
     c = d;
     cout << "<--->" << endl;
     d = foo("3rd Elem");
     cout << "<--->" << endl;
     HasPtr e(foo("4th Elem"));
 
     cout << "<----Vector Test--->" << endl;
     vector<HasPtr> vec;
     vec.push_back(a);
     cout << "<------->" << endl;
     vec.push_back(b);
     cout << "<------->" << endl;
     vec.push_back(c);
     cout << "<------->" << endl;
     vec.push_back(d);
     cout << "<------->" << endl;
     return 0;
 } 
----------------------------------------
                 OUTPUT
----------------------------------------
 i = 5
 r = 5
 rval = 10
 HasPtr()
 HasPtr(HasPtr&)
 HasPtr(HasPtr&)
 HasPtr(string*)
 operator=(HasPtr&)
 <--->
 HasPtr(string*)
 operator=(HasPtr&&)
 ~HasPtr()
 <--->
 HasPtr(string*)
 <----Vector Test--->
 HasPtr(HasPtr&)
 <------->
 HasPtr(HasPtr&)
 HasPtr(HasPtr&&)
 ~HasPtr()
 <------->
 HasPtr(HasPtr&)
 HasPtr(HasPtr&&)
 HasPtr(HasPtr&&)
 ~HasPtr()
 ~HasPtr()
 <------->
 HasPtr(HasPtr&)
 <------->
 ~HasPtr()
 ~HasPtr()
 ~HasPtr()
 ~HasPtr()
 ~HasPtr()
 ~HasPtr()
 ~HasPtr()
 ~HasPtr()
 ~HasPtr() 

16. Floating point programming

This is not related to C++ 11 but more of a general topic. I came across a code that was trying to test equality of two variable of type double. And guess what happened. The equality test failed always.

So below are some points to be noted while working with floating point numbers:

a. Don’t test for equality

The code I saw was doing:

double x, y;
x = 10.62 * 10;
y = 1062.0 / 10;
if (x == y)
cout << x << " & " << y << " are Equal" << endl;
else
cout << x << " & " << y << " are Not Equal" << endl;
---------------------------------------------------------
output:
106.2 & 106.2 are Not Equal

A solution could be to use a tolerance like below:

 double tolerence = 0.00001;
if (fabs(x - y) < tolerance)
cout << x << " & " << y << " are Equal" << endl;
else
cout << x << " & " << y << " are Not Equal" << endl;
---------------------------------------------------------
output:
106.2 & 106.2 are Equal

This is better, but now the question would be: what is the right value of tolerance?

b. Avoid addition & subtraction

Relative error in multiplication and division are always small. Addition and subtraction, on the other hand, can result in complete loss of precision.

Avoid subtracting nearly equal numbers.

17. Constant Pointers & Pointer to Constants

A constant pointer is a pointer that cannot change the address its holding. In other words, we can say that once a constant pointer points to a variable then it cannot point to any other variable.

int var1 = 0, var2 = 0;
int *const ptr = &var1; // ptr is a constant pointer and the address
                        //  contained in ptr cannot be changed 
ptr = &var2;  // This will generate compile time error 
ptr++;        // This will also generate compile time error

A pointer through which one cannot change the value of variable it points is known as a pointer to constant. These type of pointers can change the address they point to but cannot change the value kept at those address.

int var1 = 0;
const int* ptr = &var1; // This is pointer to constant
*ptr = 1;   // This will generate compile time error

What if we want a pointer that cannot change the address its holding as well as cannot change the value at the address it is pointing to. Its called a constant pointer to a constant.

int var1 = 0,var2 = 0;
const int* const ptr = &var1; // ptr is a constant pointer to a constant.
*ptr = 1;      // This will generate compile time error
ptr = &var2;   // This will generate compile time error

Leave a Reply

Your email address will not be published. Required fields are marked *