C++11 Language Features

In [ ]:
#include <iostream>
#include <vector>
#include <cmath>
#include <array>
#include <memory>
#include <chrono>
#include <string>

Initializer lists

A lightweight array-like container of elements created using a "braced list" syntax. For example, { 1, 2, 3 } creates a sequences of integers, that has type std::initializer_list. Useful as a replacement to passing a vector of objects to a function.

In [ ]:
int sum(const std::initializer_list<int>& list) {
    int total = 0;
    for (auto& e : list) {
        total += e;
    }
    return total;
}

std::cout << "Initializer lists...\n";
auto list = { 1, 2, 3, 4, 5 };
std::cout << sum(list) << "\n"; // == 15
std::cout << sum({ 1, 2, 3 }) << "\n"; // == 6
std::cout << sum({}) << "\n"; // == 0
In [3]:
//Static assertions//
//Assertions that are evaluated at compile-time.


constexpr int x = 0; // ha x = 0 -> a static_assert jelez
constexpr int y = 1;
static_assert(x == y, "x != y");
input_line_12:6:1: error: static_assert failed "x != y"
static_assert(x == y, "x != y");
^             ~~~~~~
Interpreter Error: 

auto

auto-typed variables are deduced by the compiler according to the type of their initializer.

In [ ]:
// Functions can also deduce the return type using auto. C++11//
template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x + y) {
    return x + y;
}

auto a = 3.14; // double
auto b = 1; // int
auto& c = b; // int&
auto d = { 0 }; // std::initializer_list<int>
auto&& e = 1; // int&&
auto&& f = b; // int&

const auto h = 1; // const int
auto i = 1, j = 2, k = 3; // int, int, int
//auto l = 1, m = true, n = 1.61; // error -- `l` deduced to be int, `m` is bool
//auto o; // error -- `o` requires initializer

//Extremely useful for readability, especially for complicated types:

std::vector<int> v = {1,2,3,4,5};
std::vector<int>::const_iterator cit1 = v.cbegin();
// vs.
auto cit2 = v.cbegin();

// Functions can also deduce the return type using auto. In C++11, a return type must be specified either 
// explicitly, or using decltype like so:

//Functions can also deduce the return type using auto

auto r1 = add(1, 2); // == 3
auto r2 = add(1, 2.0); // == 3.0
auto r3 = add(1.5, 1.5); // == 3.0

Lambda expressions

A lambda is an unnamed function object capable of capturing variables in scope. It features: a capture list; an optional set of parameters with an optional trailing return type; and a body. Examples of capture lists:

[] - captures nothing.
[=] - capture local objects (local variables, parameters) in scope by value.
[&] - capture local objects (local variables, parameters) in scope by reference.
[this] - capture this pointer by value.
[a, &b] - capture objects a by value, b by reference.

In [ ]:
int x1 = 10;

auto getX = [=]{ return x1; }; // elkapja x1 lokalis valtozot ertek szerint es visszadja
std::cout << getX() << "\n"; // == 10

auto addX = [=](int y) { return x1 + y; }; // elkapja x1 lokalis valtozot ertek szerint es hozzadja a parameterben
                                           // kapott erteket
std::cout << addX(1) << "\n"; // == 11

auto getXRef = [&]() -> int& { return ++x1; };
std::cout << getXRef() << " " << x1 << "\n"; // int& to `x` igy tudom belul modositani az elkapott erteket es 
                                             // kivul is valtozik

// By default, value-captures cannot be modified inside the lambda because the compiler-generated method 
// is marked as const. The mutable keyword allows modifying captured variables. The keyword is placed after 
// the parameter-list (which must be present even if it is empty).

int x2 = 1;

auto f1 = [&x2] { x2 = 2; }; // OK: x is a reference and modifies the original

//auto f2 = [x2] { x2 = 2; }; // ERROR: the lambda can only perform const-operations on the captured value
// vs.
auto f3 = [x2] () mutable { x2 = 2; }; // OK: the lambda can perform any operations on the captured value

decltype
decltype is an operator which returns the declared type of an expression passed to it. Examples of decltype:

In [ ]:
//Decltype//
template <typename X, typename Y>
auto add2(X x, Y y) -> decltype(x + y) {
    return x + y;
}

int aa = 1; // `aa` is declared as type `int`
decltype(aa) bb = aa; // `decltype(aa)` is `int`
const int& cc = aa; // `cc` is declared as type `const int&`
decltype(cc) dd = aa; // `decltype(cc)` is `const int&`
decltype(123) ee = 123; // `decltype(123)` is `int`
int&& ff = 1; // `ff` is declared as type `int&&`
decltype(ff) gg = 1; // `decltype(ff) is `int&&`
decltype((aa)) hh = gg; // `decltype((aa))` is int&
auto r4 = add2(1, 2.0); // `decltype(x + y)` => `decltype(3.0)` => `double`

nullptr
C++11 introduces a new null pointer type designed to replace C's NULL macro. nullptr itself is of type std::nullptr_t and can be implicitly converted into pointer types, and unlike NULL, not convertible to integral types except bool.

In [ ]:
void foo(int) {};
void foo(char*) {};

//foo(NULL); // error -- ambiguous
foo(nullptr); // calls foo(char*)

Template aliases
Semantically similar to using a typedef however, template aliases with using are easier to read and are compatible with templates.

In [ ]:
template <typename T>
using Vec = std::vector<T>;

using String = std::string;

Vec<int> vv{1,2,3}; // std::vector<int>
std::for_each(vv.begin(), vv.end(), [](const int i){ std::cout << i << " ";});
String str{"str"};

Strongly-typed enums
Type-safe enums that solve a variety of problems with C-style enums including: implicit conversions, inability to specify the underlying type, scope pollution.

In [ ]:
// Specifying underlying type as `unsigned int`
enum class Color : unsigned int { Red = 0xff0000, Green = 0xff00, Blue = 0xff };

// `Red`/`Green` in `Alert` don't conflict with `Color`
enum class Alert : bool { Red, Green };

//`Red` in `Alert` don't conflict with `Color`
Color ccc = Color::Red;
Alert aaa = Alert::Red;

constexpr
Constant expressions are expressions evaluated by the compiler at compile-time. Only non-complex computations can be carried out in a constant expression. Use the constexpr specifier to indicate the variable, function, etc. is a constant expression.

In [ ]:
constexpr int square(int x) {
    return x * x;
}

int square2(int x) {
    return x * x;
}

struct Complex {
    constexpr Complex(double r, double i) : re(r), im(i) { }
    constexpr double real() { return re; }
    constexpr double imag() { return im; }

private:
    double re;
    double im;
};

int ca = square(2);  // mov DWORD PTR [rbp-4], 4

int cb = square2(2); // mov edi, 2
                     // call square2(int)
                     // mov DWORD PTR [rbp-8], eax

//constexpr values are those that the compiler can evaluate at compile-time:
constexpr int cx = 123;
constexpr int cx2 = 123 + 27;

//Constant expressions with classes:
constexpr Complex I(0, 1);

Delegating constructors
Constructors can now call other constructors in the same class using an initializer list.

In [ ]:
struct Foo {
    int foo;
    Foo(int foo) : foo(foo) {}
    Foo() : Foo(0) {}
};

std::cout << "Delegating constructors...\n";
Foo foo{};
std::cout << foo.foo << "\n"; // == 0

User-defined literals
User-defined literals allow you to extend the language and add your own syntax. To create a literal, define a T operator "" X(...) { ... } function that returns a type T, with a name X. Note that the name of this function defines the name of the literal. Any literal names not starting with an underscore are reserved and won't be invoked. There are rules on what parameters a user-defined literal function should accept, according to what type the literal is called on.

In [ ]:
// `unsigned long long` parameter required for integer literal.
long long operator "" _celsius(unsigned long long tempCelsius) {
    return std::llround(tempCelsius * 1.8 + 32);
}

// `const char*` and `std::size_t` required as parameters.
int operator "" _int(const char* str, std::size_t) {
    return std::stoi(str);
}

//Converting Celsius to Fahrenheit:
std::cout << "User-defined literals...\n";
std::cout << "24 celsius = " << 24_celsius << " fahrenheit.\n"; // == 75

//String to integer conversion:
std::cout << "String to int: " << "123"_int << "\n"; // == 123, with type `int`

Range-based for loops
Syntactic sugar for iterating over a container's elements.

In [21]:
std::array<int, 5> a_{ 1, 2, 3, 4, 5 };
for (int& x : a_) 
    x *= 2;
// a_ == { 2, 4, 6, 8, 10 }    

//Note the difference when using int as opposed to int&:
    
std::array<int, 5> a__{ 1, 2, 3, 4, 5 };
for (int x : a__) 
    x *= 2;
// a__ == { 1, 2, 3, 4, 5 }

std::to_string
Converts a numeric argument to a std::string.

In [ ]:
std::to_string(1.2); // == "1.2"
std::to_string(123); // == "123"

Type traits
Type traits defines a compile-time template-based interface to query or modify the properties of types.

In [ ]:
static_assert(std::is_integral<int>::value == 1);
static_assert(std::is_same<int, int>::value == 1);
static_assert(std::is_same<std::conditional<true, int, double>::type, int>::value == 1);

std::chrono
The chrono library contains a set of utility functions and types that deal with durations, clocks, and time points. One use case of this library is benchmarking code:

In [ ]:
std::chrono::time_point<std::chrono::system_clock> start, end;
start = std::chrono::system_clock::now();
// Some computations...
end = std::chrono::system_clock::now();
    
std::chrono::duration<double> elapsed_seconds = end-start;
    
elapsed_seconds.count(); // t number of seconds, represented as a `double`

Tuples
Tuples are a fixed-size collection of heterogeneous values. Access the elements of a std::tuple by unpacking using std::tie, or using std::get.

In [ ]:
// `playerProfile` has type `std::tuple<int, std::string, std::string>`.
auto playerProfile = std::make_tuple(51, "Frans Nielsen", "NYI");
std::get<0>(playerProfile); // 51
std::get<1>(playerProfile); // "Frans Nielsen"
std::get<2>(playerProfile); // "NYI"

std::tie
Creates a tuple of lvalue references. Useful for unpacking std::pair and std::tuple objects. Use std::ignore as a placeholder for ignored values. In C++17, structured bindings should be used instead.

In [ ]:
// With tuples...
std::string playerName;
std::tie(std::ignore, playerName, std::ignore) = std::make_tuple(91, "John Tavares", "NYI");
    
// With pairs...
std::string yes, no;
std::tie(yes, no) = std::make_pair("yes", "no");

std::array
std::array is a container built on top of a C-style array. Supports common container operations such as sorting.

In [ ]:
std::array<int, 3> _a = {2, 1, 3};
std::sort(_a.begin(), _a.end()); // a == { 1, 2, 3 }
for (int& x : _a) x *= 2; // a == { 2, 4, 6 }

Unordered containers
These containers maintain average constant-time complexity for search, insert, and remove operations. In order to achieve constant-time complexity, sacrifices order for speed by hashing elements into buckets. There are four unordered containers:

unordered_set
unordered_multiset
unordered_map
unordered_multimap

std::make_shared
std::make_shared is the recommended way to create instances of std::shared_ptrs due to the following reasons:

    Avoid having to use the new operator.
    Prevents code repetition when specifying the underlying type the pointer shall hold.
    It provides exception-safety. Suppose we were calling a function foo like so:
In [ ]:
foo(std::shared_ptr<T>{ new T{} }, function_that_throws(), std::shared_ptr<T>{ new T{} });
    
// The compiler is free to call new T{}, then function_that_throws(), and so on... Since we have allocated 
// data on the heap in the first construction of a T, we have introduced a leak here. With std::make_shared, 
// we are given exception-safety:
        
foo(std::make_shared<T>(), function_that_throws(), std::make_shared<T>());

// Prevents having to do two allocations. When calling std::shared_ptr{ new T{} }, we have to allocate memory for T, 
// then in the shared pointer we have to allocate memory for the control block within the pointer.
    
// See the section on smart pointers for more information on std::unique_ptr and std::shared_ptr.

Explicit virtual overrides
Specifies that a virtual function overrides another virtual function. If the virtual function does not override a parent's virtual function, throws a compiler error.

In [ ]:
struct A {
  virtual void foo();
  void bar();
};

struct B : A {
  void foo() override; // correct -- B::foo overrides A::foo
  //void bar() override; // error -- A::bar is not virtual
};

Final specifier
Specifies that a virtual function cannot be overridden in a derived class or that a class cannot be inherited from.

In [32]:
struct AA {
  virtual void foo();
};

struct BB : AA {
  virtual void foo() final;
};

struct CC : BB {
  //virtual void foo(); // error -- declaration of 'foo' overrides a 'final' function
};

//Class cannot be inherited from.

struct AAA final {

};

//struct B : A { // error -- base 'A' is marked 'final'
//};

Default functions
A more elegant, efficient way to provide a default implementation of a function, such as a constructor.

In [33]:
struct A1 {
    A1() = default;
    A1(int x) : x(x) {}
    int x{ 1 };
};

A1 a{}; // a.x == 1
A1 a2{ 123 }; // a.x == 123

//With inheritance:
struct B1 {
    B1() : x(1){};
    int x;
};
  
struct C1 : B1 {
    // Calls B::B
    C1() = default;
};
  
C1 c{}; // c.x == 1

Deleted functions
A more elegant, efficient way to provide a deleted implementation of a function. Useful for preventing copies on objects.

In [34]:
class A2 {
    int x;
  
public:
    A2(int x) : x(x) {};
    A2(const A2&) = delete;
    A2& operator=(const A2&) = delete;
};
  
A2 x{ 123 };
//A2 y = x; // error -- call to deleted copy constructor
//y = x; // error -- operator= deleted

Special member functions for move semantics
The copy constructor and copy assignment operator are called when copies are made, and with C++11's introduction of move semantics, there is now a move constructor and move assignment operator for moves.

In [ ]:
struct Am {
    std::string s;
    Am() : s("test") {} // constructor
    Am(const Am& o) : s(o.s) {} // copy constructor
    Am(Am&& o) : s(std::move(o.s)) {} // move copy constructor

    Am& operator=(Am&& o) { // move assignment operator
        s = std::move(o.s);
        return *this;
    }
};
  
Am f(Am a) {
    return a;
}

void testMove() {
    Am a11 = f(Am{}); // move-constructed from rvalue temporary
    Am a22 = std::move(a11); // move-constructed using std::move
    Am a33 = Am{};
    a22 = std::move(a33); // move-assignment using std::move
    a11 = f(Am{}); // move-assignment from rvalue temporary
}

Non-static data member initializers
Allows non-static data members to be initialized where they are declared, potentially cleaning up constructors of default initializations.

In [36]:
// Default initialization prior to C++11
class Human {
    Human() : age(0) {}
private:
    unsigned age;
};

// Default initialization on C++11
class Human2 {
private:
    unsigned age{0};
};

C++11 Library Features

std::move
std::move indicates that the object passed to it may be moved, or in other words, moved from one object to another without a copy. The object passed in should not be used after the move in certain situations.

A definition of std::move (performing a move is nothing more than casting to an rvalue):

In [ ]:
template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
    return static_cast<typename remove_reference<T>::type&&>(arg);
}
In [ ]:
//Transferring std::unique_ptrs:

void transferSmartPtr() {
    std::unique_ptr<int> p1{ new int };
    //std::unique_ptr<int> p2 = p1; // error -- cannot copy unique pointers
    std::unique_ptr<int> p3 = std::move(p1); // move `p1` into `p2` // now unsafe to dereference object held by `p1`
}

std::forward
Returns the arguments passed to it as-is, either as an lvalue or rvalue references, and includes cv-qualification. Useful for generic code that need a reference (either lvalue or rvalue) when appropriate, e.g factories. Forwarding gets its power from template argument deduction:

T& & becomes T&
T& && becomes T&
T&& & becomes T&
T&& && becomes T&&

A definition of std::forward:

In [ ]:
template <typename T>
T&& forward(typename remove_reference<T>::type& arg) {
     return static_cast<T&&>(arg);
}
In [ ]:
//An example of a function wrapper which just forwards other A objects to a new A object's copy or move constructor:

struct Aff {
    Aff() = default;
    Aff(const Aff& o) { std::cout << "copied" << std::endl; }
    Aff(Aff&& o) { std::cout << "moved" << std::endl; }
};

template <typename T>
Aff wrapper(T&& arg) {
    return Aff{ std::forward<T>(arg) };
}

void testForward() {
    wrapper(Aff{}); // moved
    Aff a{};
    wrapper(a); // copied
    wrapper(std::move(a)); // moved
}

Smart pointers
C++11 introduces new smart(er) pointers: std::unique_ptr, std::shared_ptr, std::weak_ptr. std::auto_ptr now becomes deprecated and then eventually removed in C++17.

std::unique_ptr is a non-copyable, movable smart pointer that properly manages arrays and STL containers. Note: Prefer using the std::make_X helper functions as opposed to using constructors. See the sections for std::make_unique and std::make_shared.

In [ ]:
struct Foo2 {
    void bar(){};
};

void testUniquePtr() {
    std::unique_ptr<Foo2> p1 { new Foo2{} };  // `p1` owns `Foo`
    if (p1) p1->bar();
    
    {
        std::unique_ptr<Foo2> p2 { std::move(p1) };  // Now `p2` owns `Foo`
        p1 = std::move(p2);  // Ownership returns to `p1` -- `p2` gets destroyed
    }
    
    if (p1) p1->bar();
}

'Foo' instance is destroyed when p1 goes out of scope

A std::shared_ptr is a smart pointer that manages a resource that is shared across multiple owners. A shared pointer holds a control block which has a few components such as the managed object and a reference counter. All control block access is thread-safe, however, manipulating the managed object itself is not thread-safe.

In [ ]:
void foo(std::shared_ptr<int> t) {
  // Do something with `t`...
}

void bar(std::shared_ptr<int> t) {
  // Do something with `t`...
}

void baz(std::shared_ptr<int> t) {
  // Do something with `t`...
}

void testSharedPtr() {
    std::shared_ptr<int> p1 { new int{} };
    // Perhaps these take place in another threads?
    foo(p1);
    bar(p1);
    baz(p1);
}

std::weak_ptr is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by std::shared_ptr. It must be converted to std::shared_ptr in order to access the referenced object.

In [ ]:
std::weak_ptr<int> gw;

void f()
{
    std::cout << "use_count == " << gw.use_count() << ": ";
    if (auto spt = gw.lock()) { // Has to be copied into a shared_ptr before usage
        std::cout << *spt << "\n";
    }
    else {
        std::cout << "gw is expired\n";
    }
}

void g() {
    
    {
        auto sp = std::make_shared<int>(42);
        gw = sp;

        f();
    }

    f();
}