C++ 11

C++11, officially known as ISO/IEC 14882:2011, was released in 2011 and was a significant milestone in the evolution of the C++ programming language.

It introduced a plethora of new features and enhancements that made C++ more powerful.


Features Introduced in C++ 11

Here are the most important features introduced in C++ 11:

  • The auto keyword
  • Range based for loop
  • Lambda Expressions
  • Smart Pointers
  • The constexpr keyword
  • The nullptr keyword
  • Type traits
  • Thread support
  • Delegating constructor
  • Deleted and defaulted functions

Let's start by exploring the auto keyword.


C++ auto Keyword

The auto keyword allows C++11 to automatically deduce the type of the variable from its initializer.

#include <iostream>
using namespace std;

int main() {

// x is deduced as int auto x = 42; // pi is deduced as double auto pi = 3.1415926535;
cout << "x: " << x << endl; cout << "pi: " << pi << endl; return 0; }

Output

x: 42
pi: 3.14159

Note: auto is a type placeholder in C++, not a type itself.So, it cannot be used in casts or operators like sizeof or typeid.


Range Based for Loops

Range based for loops in C++ executes a loop for a range. The general syntax is

for (range_initialization : range_container) {
    // loop statements
}

Here,

  • range_initialization - creates an iterating variable.
  • range_container - container to iterate over.

For example,

vector<int> numbers = {1, 2, 3};

// loop through the numbers vector
for (int num : numbers) {

    // print vector element
    cout << num << endl;
}

Example 1: C++ Range Based for Loop

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

int main() {

    // create a vector of integers
    vector<int> numbers = {1, 2, 3, 4, 5};

// use a range-based for loop // to iterate through the vector for (const auto& num : numbers) { cout << num << " "; }
return 0; }

Output

1 2 3 4 5 

Here, we have declared the iterating variable num and iterated over the container numbers using a range based for loop.


Lambda Expressions

Lambda expressions allow you to create anonymous functions in a concise manner. They are especially useful when you need to pass small functions as arguments to other functions.

The general syntax is:

[capture_clause] (parameter_list) -> return_type {
    // lambda body: Code to be executed
}

Here,

  • capture_clause - specifies which variables from the surrounding scope are accessible within the lambda function.
  • parameter_list - defines the parameters the function accepts.
  • -> return_type - Declares the return type of the function.

Note: We can omit return_type for auto deduction of types unless there are multiple possible return values of different types.

To learn more, visit our C++ Lambda tutorial.


Example 2: C++ Lambda Expression

#include <iostream>
using namespace std;

int main() {

// define a lambda function named 'add' // that takes two integers and returns their sum auto add = [] (int a, int b) { return a + b; }; // call the lambda with arguments 3 and 4 int result = add(3, 4);
// print the result. cout << "Result: " << result << endl; return 0; }

Output

Result: 7

Here, we have created a lambda function add() of type int that takes in two arguments a and b and returns their sum.


Smart Pointers

C++11 introduced smart pointers that automatically manage memory and help prevent memory leaks. Basically, a smart pointer automatically releases the memory it manages when it goes out of scope.

Smart pointers are of two types:

  • Unique Pointers have exclusive ownership of the objects they point to.
  • Shared Pointers allow multiple shared pointers to own a single object.

The syntax for creating smart pointers is:

// unique pointer
std::unique_ptr<data_type> unique_pointer = std::make_unique<data_type>(args...);

// shared pointer
std::shared_ptr<Type> shared_pointer = std::make_shared<data_type>(args...);

Note: We need to import the <memory> header file to use smart pointers.


Example 3: C++ Smart Pointers

#include <iostream>
#include <memory>
using namespace std; int main() {
// create a shared pointer to an integer with value 42 shared_ptr<int> shared_pointer = make_shared<int>(42); // create a unique pointer to a double with value 3.14 unique_ptr<double> unique_pointer = make_unique<double>(3.14);
// print the value pointed to by shared_ptr cout << "shared_pointer: " << *shared_pointer << endl; // print the value pointed to by unique_ptr cout << "unique_pointer: " << *unique_pointer << endl; return 0; }

Output

shared_pointer: 42
unique_pointer: 3.14

Move Semantics

Move semantics allows the resources owned by an object to be moved into another object instead of copying them. This optimizes performance by avoiding deep copies.

We use the move() function to implement move semantics. For example,

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

int main() {

    // create an integer vector
    vector<int> source = {1, 2, 3};

// move the contents of source to another vector vector<int> destination = move(source);
// print the destination vector cout << "Destination Vector Contents: "; for (const int num : destination) { cout << num << " "; } // print the size of the destination vector cout << "\nDestination Vector Size: " << destination.size(); return 0; }

Output

Destination Vector Contents: 1  2  3  
Destination Vector Size: 3

Here, we have used the move() function to move the contents from source to destination.

vector<int> destination = move(source);

C++ constexpr Keyword

The constexpr keyword allows you to specify that a variable or function can be evaluated at compile-time. For example,

constexpr int square(int x) {
    return x * x;
}

// computed at compile-time
int result = square(5);

Here, we have used the constexpr keyword with the square() function. As a result, square(5) can be evaluated at compile time rather than run time.

This can boost the performance of the code and ensures that the expressions are initialized with a value that is known at compile time.


Null Pointer

The introduction of nullptr provides a safer alternative to using NULL for null pointers.

int* ptr = nullptr;

Type Traits

In C++, type traits are a group of templates that are used to gather information about types at compile time, and are a powerful tool for template metaprogramming.

This means that before your program runs, the compiler can understand things about the types you're using—like whether a type is an integer, if it can be copied, or if it's a class with a certain function.

They are part of the Standard Template Library (STL) and included within the <type_traits> header file.

Type traits are implemented using class templates or function templates. Here's a sample code of a function template.

template <typename T>
void process(T value) {

    if (std::is_pointer<T>::value) {
        // handle pointers
    }
    else if (std::is_integral<T>::value) {
        // handle integers
    }
}

Thread Support

C++11 added a standardized threading library that allows you to create and manage threads.

#include <thread>

void my_function() {
    // code to be executed in a separate thread
}

int main() {

    std::thread t1(my_function);
    // wait for the thread to finish
    t1.join();
    return 0;
}

Delegating Constructors

In C++11, a constructor may call another constructor of the same class. For example,

#include <iostream>
using namespace std;

class M {
    int x;
    int y;

public:

    // constructor #1 (target constructor)
    M(int a, int b) : x(a), y(b) {}

// constructor #2 (delegating constructor) // pass two zeroes as arguments to target constructor M() : M(0, 0) { cout << "Delegating constructor" << endl; cout << "x = " << x << endl; cout << "y = " << y << endl; }
}; int main() { // create an instance of class M // using the delegating constructor M m; return 0; }

Output

Delegating constructor
x = 0
y = 0

Here, the delegating constructor invokes the target constructor.


Deleted and Defaulted Functions

1. Deleted Function

They are member functions that have been intentionally marked as deleted. This means that the functions cannot be used, and any attempt to call them will result in a compilation error.

Deleting functions is useful when you want to prevent certain operations on your class.

class My_Class {
public:

    // delete the default constructor
    My_Class() = delete;

    // delete a member function
    void do_something() = delete; 
};

2. Defaulted Function

They are used to explicitly request the compiler to generate default implementations for certain member functions.

This is particularly useful when you want to take advantage of the compiler-generated versions while still providing custom behavior for other functions.

class Another_Class {
public:

    // use the compiler-generated default constructor
    Another_Class() = default;

    // define a custom constructor
    Another_Class(int x) : value(x) {} 

private:
    int value;
};

Example 4: C++ Deleted and Defaulted Functions

class Example {
public:

    // defaulted default constructor
    Example() = default;
    
    // deleted copy constructor
    Example(const Example&) = delete; 
};

int main() {

    // allowed: default constructor is used
    Example obj1; 
    
    // error: copy constructor is deleted
    // Example obj2 = obj1;

    return 0;
}

Here,

  • We can create an object of the Example class using the default constructor.
  • We cannot create a copy of an object of Example because the copy constructor is deleted.
Did you find this article helpful?