C++11/14/17 New Features

Language Features

nullptr

The null pointer and an integer 0 cannot be distinguished well for overload resolution before C++11.

  • 0 : number zero
  • void* : a pointer to an object of unknown type
  • nullptr : a pointer that does not point to an object
1
2
3
4
5
foo(int i);
foo(char* ch);

foo(NULL); // invalid, lead to ambiguity of override
foo(nullptr); // C++1x

constexpr

1
2
3
4
5
6
7
8
9
10
const int len = 5;
constexpr int fibonacci(const int n) {
if(n == 1) return 1;
if(n == 2) return 1;
return fibonacci(n-1)+fibonacci(n-2);
}

int array[5]; // Ok
int array[5 + len]; // Ok
int array[5 + fibonacci(len)]; // Ok: since C++14

auto & decltype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
auto i = 5;              // i   => int
auto arr = new auto(10); // arr => int *

for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr);

// must specify return type R, since C++98
template<typename T, typename U, typename R>
R add(T x, U y) { return x+y; }

// trailing return type using decltype, since C++11
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) { return x+y; }

// direct trailing return type, since C++14
template<typename T, typename U>
auto add(T x, U y) { return x+y; }

for

1
2
3
4
std::vector<int> nums(5);
int nums[] = {1, 2, 3, 4, 5};

for(auto &n : nums) { n++; }

Initialization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void func(std::initializer_list<int>& list) { return; }
func({1,2,3});

class Shape {
public Shape(int area, string name);
}
Shape circle = { 5, "circle" };
Shape get_circle() { return { 5, "circle" }; }

struct Square {
int area;
string name;
}
Square square = { 5, "square" };

Constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Base {
public:
int value1_, value2_;
Base(int value) : value1_(value) { }

// delegating constructor
Base(int value1, int value2) : Base(value1) {
value2_ = value2;
}

// invalid, an initializer for a delegating constructor must appear alone
Base(int value1, int value2)
: Base(value1)
, value2_(value2)
{}

// explicitly use default constructor
Base() = default;
// explicitly disable default copy constructor
Base& operator=(const Base&) = delete;
};

class Subclass : public Base {
public:
// inherit base class constructor without passing parameters
using Base::Base;
};

typedef & using

1
2
3
4
5
6
7
// both can redefine type name
typedef OldType NewType;
using NewType = OldType;

// however, only `using` can redefine template name, since C++11
template <typename T>
using NewTemplate = OldTemplate<int, T, 1>;

Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// default template typename
template<typename T = int, typename U = int>
auto add(T x, U y) { return x+y; }

// variadic template
// 1. recursive template function (cons: must define a terminal function)
template<typename T>
void printf(T value) {
std::cout << value << std::endl;
}
template<typename T, typename... Args>
void printf(T value, Args... args) {
std::cout << sizeof...(args) << std::endl; // get number of Args
std::cout << value << std::endl;
printf(args...);
}

// 2. magic, use initializer_list with lambda expression
template<typename T, typename... Args>
auto print(T value, Args... args) {
std::cout << value << std::endl;
return std::initializer_list<T>{
([&]{ std::cout << args << std::endl; }(), value)...
};
}

Enhanced Object Orientation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// `override` : show explicitly
struct Base {
virtual void foo(int);
};
struct SubClass: Base {
virtual void foo(int) override; // valid
virtual void foo(float) override; // invalid, not exist in parent class
};

// `final` : no more inheritance
struct Base { virtual void foo() final; };
struct SubClass1 final: Base { };
struct SubClass2 : SubClass1 { }; // invalid, SubClass1 is final
struct SubClass3 : Base { void foo(); }; // invalid, foo is final

Enum Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// enum class
enum class color_t : unsigned int {
red = 1,
blue = 2,
dark_blue = 2
};
if (color_t::blue == color_t::dark_blue) { true; }

// override `<<` for enum type
template<typename T>
std::ostream& operator<<(
typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream,
const T& e)
{
return stream << static_cast<typename std::underlying_type<T>::type>(e);
}

lambda expression

lambda expression is a compiler generated functor (a class override () operator).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// [ captures ] ( params ) -> ret { body }

// implicitly capture (`=`, `&`, `this` ...)
auto foo = [&]() { return 0; };

// the type of a closure cannot be named, but can be inferred with auto
int x = 4;
auto y = [&r = x, x = x + 1]() -> int { // initializer action, since C++14
r += 2;
return x;
}(); // updates ::x to 6 and initializes y to 5.

// generic lambda using `auto`, since C++14
auto generic = [](auto x, auto y) { return x+y; };

std::functional & std::bind

1
2
3
4
5
6
7
8
9
10
11
12
13
int foo(int a, int b, int c) { ; }

int main() {
// closures can be captured in `std::function` like all callable objects
// (this may incur unnecessary overhead)
std::function<int(int)> bar = [](int i) { return i + 4; };

// bind params 1 and 2 to foo
// however, use std::placeholders::_1 to take the place of first param.
auto bind_foo = std::bind(foo, std::placeholders::_1, 1,2);
// when calling bind_foo, only need to provide the first param
bind_foo(1);
}

lvalue and rvalue

lvalue refers to an object that persists beyond a single expression. You can think of an lvalue as an object that has a name. All variables, including nonmodifiable (const) variables, are lvalues.

An rvalue is a temporary value that does not persist beyond the expression that uses it. An rvalue may be used to initialize an rvalue reference, in which case the lifetime of the object identified by the rvalue is extended until the scope of the reference ends.

  • std::move allows you to treat any expression as though it represents a value
  • std::forward allows you to preserve whether an expression represented an object or a value
relationships between value categories

relationships between value categories

Rvalue references solve at least two problems:

  1. Implementing move semantics
  2. Perfect forwarding
1
2
template< class T >
constexpr typename std::remove_reference<T>::type&& move( T&& t ) noexcept;

Containers

std::array

Why std::array instead of std::vector or int arr[5]?

  • Use stack instead of heap, faster than std::vector
  • Encapsulate some modern operating methods like std::sort()
  • Fixed size at compile time
1
2
3
4
5
std::array<int, 4> arr= {1,2,3,4};
std::sort(arr.begin(), arr.end());

int *p = arr.data(); // = &arr[0]
size_t sz = arr.size();

std::forward_list

  • A singly-linked list supports O(1) insertion and removal of elements
  • More space efficient compared to std::list when bidirectional iteration is not needed

std::unordered_map


Pointers

std::shared_ptr

The shared_ptr class describes an object that uses reference counting to manage resources. A shared_ptrobject effectively holds a pointer to the resource that it owns or holds a nullptr. A resource can be owned by more than one shared_ptr object; when the last shared_ptr object that owns a particular resource is destroyed, the resource is freed. A shared_ptr stops owning a resource when it is reassigned or reset.

The template argument T might be an incomplete type except as noted for certain member functions. When a shared_ptr<T> object is constructed from a resource pointer of type G* or from a shared_ptr<G>, the pointer type G* must be convertible to T*. If it is not, the code will not compile.

1
2
3
4
5
6
7
8
9
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // reference count +1
auto pointer3 = pointer; // reference count +1
int *p = pointer.get(); // won't increase reference count

pointer.use_count(); // look up reference count (= 3)
pointer2.reset(); // reset pointer2, ref count -1 for other pointers
pointer.use_count(); // reference count (= 2)
pointer2.use_count(); // reference count (= 0)

std::unique_ptr

Stores a pointer to an owned object or array. The object/array is owned by no other unique_ptr. A unique_ptrobject may be moved, but not copied. The object/array is destroyed when the unique_ptr is destroyed.

1
2
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::unique_ptr<int> p2 (std::move(p1));

std::weak_ptr

std::weak_ptr describes an object that points to a resource that is managed by one or more shared_ptr objects. The weak_ptr objects that point to a resource do not affect the resource’s reference count. Thus, when the last shared_ptr object that manages that resource is destroyed the resource will be freed, even if there are weak_ptr objects pointing to that resource. This is essential for avoiding cycles in data structures.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct A;
struct B;
struct A {
std::shared_ptr<B> pointer;
};
struct B {
std::shared_ptr<A> pointer; // replaced by weak_ptr will solve the problem
};

int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;

return 0;
}

Regular Expression

std::regex

1
2
3
4
5
6
std::string fnames[] = {
"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
std::regex txt_regex("[a-z]+\\.txt");
for (const auto &fname: fnames) {
std::regex_match(fname, txt_regex); // true, if match; otherwise, false
}

Multithread

std::thread