Обновления C++: подборка изменений из трех стандартов языка

Обновления C++, внесенные стандартами C++11, C++14 и C++17. Большая подборка нововведений самого языка и изменений стандартной библиотеки.

Обновления C++17

Вывод аргументов для шаблонов классов

Теперь аргументы шаблонов автоматически выводятся не только для функций, но и для классов.

template <typename T = float>
struct MyContainer {
  T val;
  MyContainer() : val() {}
  MyContainer(T val) : val(val) {}
  // ...
};
MyContainer c1{ 1 }; // OK MyContainer<int>
MyContainer c2; // OK MyContainer<float>

Объявление not-type шаблонных параметров с помощью auto

Если тип объекта входит в список not-type template, он может быть использован в качестве аргумента шаблона с помощью auto.

template <auto ... seq>
struct my_integer_sequence {
  // реализация...
};

// Явная передача типа ' int` в качестве аргумента шаблона
auto seq = std::integer_sequence<int, 0, 1, 2>();
// Вывод типа `int`
auto seq2 = my_integer_sequence<0, 1, 2>();

Выражения свертки

Поддерживается два типа свертки шаблонных параметров:

  • Унарная – выражения вида (... op e) или (e or …), где op – это оператор, а e – нераскрытая группа параметров.
  • Бинарная – выражения вида (e1 op … op e2), где либо e1, либо e2 (но не оба сразу) является нераскрытой группой параметров.
template<typename... Args>
bool logicalAnd(Args... args) {
    // Binary folding.
    return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true
template<typename... Args>
auto sum(Args... args) {
    // Unary folding.
    return (... + args);
}
sum(1.0, 2.0f, 3); // == 6.0

Новые правила вывода типа auto при фигурной инициализации

Изменился вывод auto при использовании универсального синтаксиса инициализации. Раньше для auto x{ 3 } тип выводился как std::initializer_list<int>, сейчас выводится int.

auto x1{ 1, 2, 3 }; // ошибка: несколько элементов
auto x2 = { 1, 2, 3 }; // присваивание: decltype(x2) is std::initializer_list<int>
auto x3{ 3 }; // тип выводится из типа элемента: decltype(x3) is int
auto x4{ 3.0 }; // тип выводится из типа элемента:  decltype(x4) is double

Constexpr лямбды

Лямбды, выполняющиеся во время компиляции, с использованием constexpr.

auto identity = [] (int n) constexpr { return n; };
static_assert(identity(123) == 123);
constexpr auto add = [] (int x, int y) {
  auto L = [=] { return x; };
  auto R = [=] { return y; };
  return [=] { return L() + R(); };
};

static_assert(add(1, 2)() == 3);
constexpr int addOne(int n) {
  return [n] { return n + 1; }();
}

static_assert(addOne(1) == 2);

Захват this в лямбдах

Ранее захват this в лямбдах происходил только по ссылке. В C++17 *this теперь делает копию текущего объекта.

struct MyObj {
  int value{ 123 };
  auto getValueCopy() {
    return [*this] { return value; };
  }
  auto getValueRef() {
    return [this] { return value; };
  }
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321;
valueCopy(); // 123
valueRef(); // 321

inline переменные

Спецификатор inline может применяться как к переменным, так и к функциям.

// Пример дизассемблирования
struct S { int x; };
inline S x1 = S{321}; // mov esi, dword ptr [x1]
                      // x1: .long 321

S x2 = S{123};        // mov eax, dword ptr [.L_ZZ4mainE2x2]
                   с помощью обозревателя компиляторов    // mov dword ptr [rbp - 8], eax
                      // .L_ZZ4mainE2x2: .long 123

Вложенные пространства имен

Используйте оператор разрешения пространств имен для создания вложенных определений.

namespace A {
  namespace B {
    namespace C {
      int i;
    }
  }
}
// vs.
namespace A::B::C {
  int i;
}

Структурированные привязки

В обновления C++17 входит новый синтаксис для деструктурирующей инициализации вида auto[ x,y,z ] = expr, где тип expr – это кортежеподобный объект. Подробнее о привязках вы можете узнать здесь.

using Coordinate = std::pair<int, int>;
Coordinate origin() {
  return Coordinate{0, 0};
}

const auto [ x, y ] = origin();
x; // == 0
y; // == 0

Условные операторы с инициализацией

Новые версии инструкций if и switch позволяют упростить шаблоны кода.

{
  std::lock_guard<std::mutex> lk(mx);
  if (v.empty()) v.push_back(val);
}
// vs.
if (std::lock_guard<std::mutex> lk(mx); v.empty()) {
  v.push_back(val);
}
Foo gadget(args);
switch (auto s = gadget.status()) {
  case OK: gadget.zip(); break;
  case Bad: throw BadFoo(s.message());
}
// vs.
switch (Foo gadget(args); auto s = gadget.status()) {
  case OK: gadget.zip(); break;
  case Bad: throw BadFoo(s.message());
}

constexpr if

Позволяет выполнять if-конструкции во время компиляции.

template <typename T>
constexpr bool isIntegral() {
  if constexpr (std::is_integral<T>::value) {
    return true;
  } else {
    return false;
  }
}
static_assert(isIntegral<int>() == true);
static_assert(isIntegral<char>() == true);
static_assert(isIntegral<double>() == false);
struct S {};
static_assert(isIntegral<S>() == false);

Символьные литералы UTF-8

Начинаются с u8 и имеют тип char. Значение символьного литерала UTF-8 равно его ISO 10646 значению.

char x = u8'x';

Прямая инициализация списка перечислений

Перечисления теперь могут быть инициализированы с использованием braced-синтаксиса.

enum byte : unsigned char {};
byte b{0}; // OK
byte c{-1}; // ERROR
byte d = byte{1}; // OK
byte e = byte{256}; // ERROR

Обновления библиотеки С++17

std::variant

Типобезопасный union. В любой момент времени содержит значение одного из своих альтернативных типов (или вообще не имеет значения).

std::variant<int, double> v{ 12 };
std::get<int>(v); // == 12
std::get<0>(v); // == 12
v = 12.0;
std::get<double>(v); // == 12.0
std::get<1>(v); // == 12.0

std::optional

Необязательное значение. Может использоваться в функциях или условиях.

std::optional<std::string> create(bool b) {
  if (b) {
    return "Godzilla";
  } else {
    return {};
  }
}

create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// опционально-возвращаемые фабричные функции могут использоваться в качестве условий while и if
if (auto str = create(true)) {
  // ...
}

 

std::any

Типобезопасный контейнер для единственного значения любого типа.

std::any x{ 5 };
x.has_value() // == true
std::any_cast<int>(x) // == 5
std::any_cast<int&>(x) = 10;
std::any_cast<int>(x) // == 10

std::string_view

Ссылается на строку, но не владеет ей. Полезно для предоставления абстракции поверх строк (например, для синтаксического анализа).

// обычные строки
std::string_view cppstr{ "foo" };
// wide-строки
std::wstring_view wcstr_v{ L"baz" };
// массивы символов
char array[3] = {'b', 'a', 'r'};
std::string_view array_v(array, std::size(array));
std::string str{ "   trim me" };
std::string_view v{ str };
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
str; //  == "   trim me"
v; // == "trim me"

std::invoke

Вызывает Callable объект (std::function или std::bind) с параметрами.

template <typename Callable>
class Proxy {
    Callable c;
public:
    Proxy(Callable c): c(c) {}
    template <class... Args>
    decltype(auto) operator()(Args&&... args) {
        // ...
        return std::invoke(c, std::forward<Args>(args)...);
    }
};
auto add = [] (int x, int y) {
  return x + y;
};
Proxy<decltype(add)> p{ add };
p(1, 2); // == 3

std::apply

Вызывает объект класса Callable с кортежем аргументов.

auto add = [] (int x, int y) {
  return x + y;
};
std::apply(add, std::make_tuple( 1, 2 )); // == 3

std::filesystem

Предоставляет стандартный способ управления файлами, каталогами и путями в файловой системе.
Пример копирования большого файла во временный путь:

const auto bigFilePath {"bigFileToCopy"};
if (std::filesystem::exists(bigFilePath)) {   
  const auto bigFileSize {std::filesystem::file_size(bigFilePath)};
  std::filesystem::path tmpPath {"/tmp"};
  if (std::filesystem::space(tmpPath).available > bigFileSize) {
    std::filesystem::create_directory(tmpPath.append("example"));
    std::filesystem::copy_file(bigFilePath, tmpPath.append("newFile"));
  }
}

std::byte

Обеспечивает стандартный способ представления данных в двоичном виде. В отличие от char или unsigned char доступны только побитовые операции.

std::byte a {0};
std::byte b {0xFF};
int i = std::to_integer<int>(b); // 0xFF
std::byte c = a & b;
int j = std::to_integer<int>(c); // 0

std::byte - это просто перечисление, инициализация через фигурные скобки возможна благодаря прямой инициализации перечислений.

Манипуляции с мапами и множествами

Перемещения элементов и слияние контейнеров без затрат на создание копий и выделение/освобождение памяти.

Перемещение элементов из одного мапа в другой:

std::map<int, string> src{ { 1, "one" }, { 2, "two" }, { 3, "buckle my shoe" } };
std::map<int, string> dst{ { 3, "three" } };
dst.insert(src.extract(src.find(1))); // Cheap remove and insert of { 1, "one" } from `src` to `dst`.
dst.insert(src.extract(2)); // Cheap remove and insert of { 2, "two" } from `src` to `dst`.
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };

Вставка целого множества:

std::set<int> src{1, 3, 5};
std::set<int> dst{2, 4, 5};
dst.merge(src);
// src == { 5 }
// dst == { 1, 2, 3, 4, 5 }

Вставка элементов без контейнера:

auto elementFactory() {
  std::set<...> s;
  s.emplace(...);
  return s.extract(s.begin());
}
s2.insert(elementFactory());

Изменение ключа элемента в мапе:

std::map<int, string> m{ { 1, "one" }, { 2, "two" }, { 3, "three" } };
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }

Параллельные алгоритмы

Многие из алгоритмов STL начали поддерживать политики параллельного выполнения: seq (последовательное), par (параллельное) и par_unseq (параллельное неупорядоченное).

std::vector<int> longVector;
// Поиск элемента с использованием политики параллельного выполнения
auto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longVector), 2);
// Сортировка элементов с использование политики последовательного выполнения
auto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longVector));

Обновления C++14

Двоичные литералы

Удобный способ представления base-2 чисел с разделителем '.

0b110 // == 6
0b1111'1111 // == 255

Обобщенные лямбда-выражения

C++14 теперь позволяет использовать спецификатор типа auto в списке параметров, создавая полиморфные лямбды.

auto identity = [](auto x) { return x; };
int three = identity(3); // == 3
std::string foo = identity("foo"); // == "foo"

Инициализация лямбда-захватов

Позволяет создавать лямбда-захваты, инициализированные произвольными выражениями при создании лямбды.

int factory(int i) { return i * 10; }
auto f = [x = factory(2)] { return x; }; // возвращает 20

auto generator = [x = 0] () mutable {
   // без `mutable` не скомпилируется
   // поскольку мы изменяем x при каждом вызове
  return x++;
};
auto a = generator(); // == 0
auto b = generator(); // == 1
auto c = generator(); // == 2

Теперь можно захватывать move-only типы по значению. В примере ниже p в списке захвата task2 – это новая переменная.

auto p = std::make_unique<int>(1);
auto task1 = [=] { *p = 5; }; // ошибка: нельзя скопировать std::unique_ptr
// vs.
auto task2 = [p = std::move(p)] { *p = 5; }; // OK: p перемещается в объект замыкания
// исходный p пуст после создания task2

Имя, присвоенное захваченному значению, не обязательно должно совпадать с именем указанной переменной.

auto x = 1;
auto f = [&r = x, x = x * 10] {
  ++r;
  return r + x;
};
f(); // устанавливает для x значение 2 и возвращает 12

Определение типа возвращаемого значения

При использовании типа auto для возвращаемого значения в C++14, компилятор попытается вывести тип самостоятельно.

// тип возвращаемого значения `int`.
auto f(int i) {
 return i;
}
template <typename T>
auto& f(T& t) {
  return t;
}

// Возвращает ссылку на выведенный тип
auto g = [](auto& x) -> auto& { return f(x); };
int y = 123;
int& z = g(y); // ссылка на `y`

decltype(auto)

Спецификатор типа decltype(auto) выводит тип, как auto, но сохраняет ссылки и CV-квалификаторы.

const int x = 0;
auto x1 = x; // int
decltype(auto) x2 = x; // const int
int y = 0;
int& y1 = y;
auto y2 = y1; // int
decltype(auto) y3 = y1; // int&
int&& z = 0;
auto z1 = std::move(z); // int
decltype(auto) z2 = std::move(z); // int&&
// возвращаемый тип `int`.
auto f(const int& i) {
 return i;
}

// возвращаемый тип  `const int&`.
decltype(auto) g(const int& i) {
 return i;
}

int x = 123;
static_assert(std::is_same<const int&, decltype(f(x))>::value == 0);
static_assert(std::is_same<int, decltype(f(x))>::value == 1);
static_assert(std::is_same<const int&, decltype(g(x))>::value == 1);

Ослабление ограничений для constexpr функций

Обновления C++14 этот значительно расширили набор конструкций, допустимых в constexpr функциях: добавились if-операторы, множественные return, циклы и т.д.

constexpr int factorial(int n) {
  if (n <= 1) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}
factorial(5); // == 120

Шаблоны переменных

C++14 позволяет шаблонизировать переменные.

template<class T>
constexpr T pi = T(3.1415926535897932385);
template<class T>
constexpr T e  = T(2.7182818284590452353);

Обновления библиотеки C++14

Пользовательские литералы

Новые пользовательские литералы для типов стандартной библиотеки chrono и basic_string.

using namespace std::chrono_literals;
auto day = 24h;
day.count(); // == 24
std::chrono::duration_cast<std::chrono::minutes>(day).count(); // == 1440

Целочисленные последовательности

std::integer_sequence представляет последовательности целых чисел.

  • std::make_integer_sequence<T, N...> - создает последовательности 0, ..., N - 1 с типом T.
  • std::index_sequence_for - преобразует пакет параметров шаблона в целочисленную последовательность.

Преобразование массива в кортеж:

template<typename Array, std::size_t... I>
decltype(auto) a2t_impl(const Array& a, std::integer_sequence<std::size_t, I...>) {
  return std::make_tuple(a[I]...);
}

template<typename T, std::size_t N, typename Indices = std::make_index_sequence<N>>
decltype(auto) a2t(const std::array<T, N>& a) {
  return a2t_impl(a, Indices());
}

std::make_unique

std::make_unique рекомендуется использовать для создания экземпляров std::unique_ptr, так как эта функция позволяет:

  • избежать использования оператора new;
  • предотвратить дублирование кода при указании базового типа, который должен содержать указатель;
  • обеспечить безопасность при исключениях.

Предположим, мы вызываем функцию foo следующим образом:

foo(std::unique_ptr<T>{ new T{} }, function_that_throws(), std::unique_ptr<T>{ new T{} });

Поскольку мы выделили данные в куче в первой конструкции T, здесь происходит утечка. std::make_unique обеспечивает безопасность при выбросе исключений:

foo(std::make_unique<T>(), function_that_throws(), std::make_unique<T>());

Обновления C++11

Семантика перемещения

Семантика перемещения связана с оптимизацией производительности – возможностью перемещения объекта без больших затрат на копирование.

Передача ресурсов, при которой они остаются на своем месте в памяти, полезна при работе с rvalue.

Перемещения также позволяют передавать объекты std::unique_ptr.

Ссылки rvalue

В C++11 появились новые ссылки rvalue с синтаксисом A&&.

Автоматическое определение типа со значениями lvalue и rvalue:

int x = 0; // `x` – lvalue типа `int`
int& xl = x; // `xl` – lvalue типа `int&`
int&& xr = x; // Ошибка компиляции: `x` – lvalue
int&& xr2 = 0; // `xr2` – lvalue типа `int&&`
auto& al = x; // `al` – lvalue типа `int&`
auto&& al2 = x; // `al2` – lvalue типа `int&`
auto&& ar = 0; // `ar` – lvalue типа `int&&`

Шаблоны с переменным числом аргументов

Синтаксис ... создает пакет параметров или расширяет его. Шаблон с хотя бы одним пакетом параметров называется вариативным шаблоном.

template <typename... T>
struct arity {
  constexpr static int value = sizeof...(T);
};
static_assert(arity<>::value == 0);
static_assert(arity<char, short, int>::value == 3);

Списки инициализации

Массивоподобные контейнеры элементов, созданные с использованием braced-синтаксиса. Например, { 1, 2, 3 } создает последовательность целых чисел с типом std:: initializer_list<int>.

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

  return total;
}

auto list = { 1, 2, 3 };
sum(list); // == 6
sum({ 1, 2, 3 }); // == 6
sum({}); // == 0

Статические утверждения

Утверждения, вычисляемые во время компиляции.

constexpr int x = 0;
constexpr int y = 1;
static_assert(x == y, "x != y");

auto

Переменные с типом auto выводятся компилятором в зависимости от типа их инициализатора.

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&
auto g = new auto(123); // 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; // ошибка - " l "выводится как int," m` является bool
auto o; // ошибка - `o` требует инициализатора

Это существенно улучшает читаемость:

std::vector<int> v = ...;
std::vector<int>::const_iterator cit = v.cbegin();
// vs.
auto cit = v.cbegin();

Функции также могут выводить возвращаемый тип с помощью auto.

template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x + y) {
  return x + y;
}
add(1, 2); // == 3
add(1, 2.0); // == 3.0
add(1.5, 1.5); // == 3.0

Лямбда-выражения

Лямбда – это безымянные объекты функций, способные захватывать переменные в области видимости. У них есть список захвата (capture list):

  • []- ничего не захватывает.
  • [ = ]- захват локальных объектов по значению.
  • [ & ]- захват локальных объектов по ссылке.
  • [this] - захват указателя this по значению.
  • [a, &b] - захват объекта a по значению, b – по ссылке.
int x = 1;

auto getX = [=]{ return x; };
getX(); // == 1

auto addX = [=](int y) { return x + y; };
addX(1); // == 2

auto getXRef = [&]() -> int& { return x; };
getXRef(); // int& к `x`

По умолчанию объекты, захваченные по значению, не могут быть изменены внутри лямбда-выражения, так как созданный компилятором метод помечен как const. Ключевое слово mutable позволяет изменять их.

int x = 1;

auto f1 = [&x] { x = 2; }; // OK: x является ссылкой и изменяет оригинал

auto f2 = [x] { x = 2; }; // Ошибка: лямбда-выражение может выполнять только const-операции с захваченным значением
// vs.
auto f3 = [x] () mutable { x = 2; }; // OK: лямбда-выражение может выполнять любые операции с захваченным значением

decltype

decltype – оператор, возвращающий объявленный тип переданного ему выражения. CV-квалификаторы и ссылки сохраняются, если они являются частью выражения.

int a = 1; // `a` объявлено `int`
decltype(a) b = a; // `decltype(a)` - `int`
const int& c = a; // `c` объявлено как `const int&`
decltype(c) d = a; // `decltype(c)` - `const int&`
decltype(123) e = 123; // `decltype(123)` is `int`
int&& f = 1; // `f` объявлено как `int&&`
decltype(f) g = 1; // `decltype(f) - `int&&`
decltype((a)) h = g; // `decltype((a))` - int&
template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x + y) {
  return x + y;
}
add(1, 2.0); // `decltype(x + y)` => `decltype(3.0)` => `double`

Псевдонимы шаблонов

Псевдонимы шаблонов семантически похожи на typedef, однако они легче читаются и совместимы с шаблонами.

template <typename T>
using Vec = std::vector<T>;
Vec<int> v{}; // std::vector<int>

using String = std::string;
String s{"foo"};

nullptr

В C++11 появился новый тип null-указателя, предназначенный для замены макроса NULL языка C.

void foo(int);
void foo(char*);
foo(NULL); // ошибка - неоднозначно
foo(nullptr); // вызывает foo(char*)

Строго типизированные перечисления

Типобезопасные перечисления, которые решают множество проблем с перечислениями в C, включая неявные преобразования, невозможность указать базовый тип, загрязнение области видимости.

// Указание базового типа как " unsigned int`
enum class Color : unsigned int { Red = 0xff0000, Green = 0xff00, Blue = 0xff };
// `Red`/`Green` в `Alert` не конфликтуют с `Color`
enum class Alert : bool { Red, Green };
Color c = Color::Red;

Атрибуты

Атрибуты создают универсальный синтаксис над __attribute__(...), __declspec и т. п.

// `noreturn` атрибут указывает, что' f` не возвращается
[[ noreturn ]] void f() {
  throw "error";
}

constexpr

Выражения, которые вычисляются во время компиляции. В константном выражении могут выполняться только несложные вычисления.

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

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

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

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

Константные выражения с классами:

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;
};

constexpr Complex I(0, 1);

Делегирование конструкторов

Конструкторы теперь могут вызывать другие конструкторы в том же классе с помощью списка инициализаторов.

struct Foo {
  int foo;
  Foo(int foo) : foo(foo) {}
  Foo() : Foo(0) {}
};

Foo foo{};
foo.foo; // == 0

Пользовательские литералы

Пользовательские литералы позволяют расширить язык и добавить собственный синтаксис.

Преобразование Цельсия в градусы Фаренгейта:

// `unsigned long long` параметр, необходимый для целочисленного литерала
long long operator "" _celsius(unsigned long long tempCelsius) {
  return std::llround(tempCelsius * 1.8 + 32);
}
24_celsius; // == 75

Конвертация строки в целое число:

// `const char*` и `std::size_t` требуются в качестве параметров
int operator "" _int(const char* str, std::size_t) {
  return std::stoi(str);
}

"123"_int; // == 123 с типом `int`

Явные замещения виртуальных функций

Указывает, что виртуальная функция переопределяет другую виртуальную функцию.

struct A {
  virtual void foo();
  void bar();
};

struct B : A {
  void foo() override; // correct -- B::foo переопределяет A::foo
  void bar() override; // error -- A::bar не виртуальная функция
  void baz() override; // error -- B::baz не переопределяет A::baz
};

Спецификатор final

Указывает, что виртуальная функция не может быть переопределена в производном классе или, что класс не может быть от нее унаследован.

struct A {
  virtual void foo();
};

struct B : A {
  virtual void foo() final;
};

struct C : B {
  virtual void foo(); // ошибка: объявление 'foo' переопределяет финальную функцию
};

Функции по умолчанию

Обновления C++11 вводят более элегантный и эффективный способ обеспечить реализацию функции по умолчанию, например конструктора.

struct A {
  A() = default;
  A(int x) : x(x) {}
  int x{ 1 };
};
A a{}; // a.x == 1
A a2{ 123 }; // a.x == 123

С наследованием:

struct B {
  B() : x(1);
  int x;
};

struct C : B {
  // Calls B::B
  C() = default;
};

C c{}; // c.x == 1

Удаленные функции

Более элегантный и эффективный способ обеспечить удаленную реализацию функции. Полезно для предотвращения копирования объектов.

class A {
  int x;

public:
  A(int x) : x(x) {};
  A(const A&) = delete;
  A& operator=(const A&) = delete;
};

A x{ 123 };
A y = x; // ошибка - вызов удаленного конструктора копирования
y = x; // ошибка - operator= deleted

For-циклы по коллекциям

Синтаксический сахар для перебора элементов контейнера (range-based for).

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

Обратите внимание на разницу при использовании int, а не int&:

std::array<int, 5> a{ 1, 2, 3, 4, 5 };
for (int x : a) x *= 2;
// a == { 1, 2, 3, 4, 5 }

Специальные функции семантики перемещения

С введением семантики перемещения в C++11 теперь есть конструктор перемещения и перемещающий оператор присваивания.

struct A {
  std::string s;
  A() : s("test") {}
  A(const A& o) : s(o.s) {}
  A(A&& o) : s(std::move(o.s)) {}
  A& operator=(A&& o) {
   s = std::move(o.s);
   return *this;
  }
};

A f(A a) {
  return a;
}

A a1 = f(A{}); // конструктор перемещения из временного rvalue 
A a2 = std::move(a1); // конструктор перемещения с использованием std::move
A a3 = A{};
a2 = std::move(a3); // присваивание с перемещением с использованием std::move
a1 = f(A{}); // присваивание с перемещением из временного rvalue

Преобразование конструкторов

Преобразует braced list в аргументы конструктора.

struct A {
  A(int) {}
  A(int, int) {}
  A(int, int, int) {}
};

A a{0, 0}; // вызывает A::A(int, int)
A b(0, 0); // вызывает A::A(int, int)
A c = {0, 0}; // вызывает A::A(int, int)
A d{0, 0, 0}; // вызывает A::A(int, int, int)

Явные функции преобразования

Функции преобразования теперь можно сделать явными с помощью спецификатора explicit.

struct A {
  operator bool() const { return true; }
};

struct B {
  explicit operator bool() const { return true; }
};

A a{};
if (a); // OK вызывает A::operator bool()
bool ba = a; // OK copy-инициализация выбирает a::оператор bool()

B b{};
if (b); // OK вызывает B::operator bool()
bool bb = b; // ошибка copy-инициализация не учитывает B::оператор bool()

Встроенные пространства имен

Все члены встроенного пространства имен обрабатываются так, как если бы они были частью родительского пространства имен.

namespace Program {
  namespace Version1 {
    int getVersion() { return 1; }
    bool isFirstVersion() { return true; }
  }
  inline namespace Version2 {
    int getVersion() { return 2; }
  }
}

int version {Program::getVersion()};              // использует getVersion() их Version2
int oldVersion {Program::Version1::getVersion()}; // использует getVersion() из Version1
bool firstVersion {Program::isFirstVersion()};    // не компилируется, когда добавляется Version2

Инициализаторы нестатических членов-данных

Позволяет инициализировать нестатические члены-данных там, где они объявлены.

// Инициализация по умолчанию до C++11
class Human {
    Human() : age(0) {}
  private:
    unsigned age;
};
// Инициализация по умолчанию в C++11
class Human {
  private:
    unsigned age{0};
};

Правые угловые скобки

Теперь необязательно добавлять пробелы между закрывающими угловыми скобками.

typedef std::map<int, std::map <int, std::map <int, int> > > cpp98LongTypedef;
typedef std::map<int, std::map <int, std::map <int, int>>>   cpp11LongTypedef;

Обновления библиотеки C++11

std::move

Указывает, что переданный объект может быть перемещен из одного объекта в другой без копирования.

Выполнение перемещения – это просто приведение аргумента к rvalue:

template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

Передача std::unique_ptr:

std::unique_ptr<int> p1{ new int };
std::unique_ptr<int> p2 = p1; // ошибка - нельзя копировать уникальные указатели
std::unique_ptr<int> p3 = std::move(p1); // перемещает `p1` в `p3`
                                         // теперь небезопасно разыменовывать объект, удерживаемый `p1`

std::forward

Может принимать на вход lvalue или rvalue и возвращать их как есть, без изменений, включает cv-квалификацию:

  • Т& & становится Т&;
  • Т& && становится Т&;
  • Т&& & становится Т&;
  • T&& && становится T&&;

Определение std:forward:

template <typename T>
T&& forward(typename remove_reference<T>::type& arg) {
  return static_cast<T&&>(arg);
}

Пример функции wrapper, которая просто пересылает другие A объекты в новый конструктор копирования или перемещения объекта A:

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

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

wrapper(A{}); // moved
A a{};
wrapper(a); // copied
wrapper(std::move(a)); // moved

std::thread

Библиотека std::thread предоставляет стандартный способ управления потоками.

void foo(bool clause) { /* do something... */ }

std::vector<std::thread> threadsVector;
threadsVector.emplace_back([]() {
    // Лямбда-функция, которая будет вызвана    
});
threadsVector.emplace_back(foo, true); // поток запустит foo(true)
for (auto& thread : threadsVector)
    thread.join(); // ожидание окончания работы потоков

std::to_string

Преобразует числовой аргумент в строку std::string.

std::to_string(1.2); // == "1.2"
std::to_string(123); // == "123"

Признаки типа

Признаки типа определяет интерфейс на основе шаблона времени компиляции для запроса или изменения свойств типов.

static_assert(std::is_integral<int>::value);
static_assert(std::is_same<int, int>::value);
static_assert(std::is_same<std::conditional<true, int, double>::type, int>::value);

Умные указатели

В обновления C++11 входят новые умные указатели: std::unique_ptr, std::shared_ptr, std::weak_ptr.

std::unique_ptr<Foo> p1 { new Foo{} };  // `p1` владеет `Foo`
if (p1) p1->bar();

{
  std::unique_ptr<Foo> p2 { std::move(p1) };  // теперь `p2` владеет `Foo`
  f(*p2);

  p1 = std::move(p2);  // Собственность возвращается к 'p1` - ` p2' уничтожается
}

if (p1) p1->bar();
// Экземпляр 'Foo` уничтожается, когда `p1` выходит из области видимости

std:: chrono

Библиотека chrono содержит набор служебных функций и типов, которые имеют дело с длительностью, часами и временными точками.

std::chrono::time_point<std::chrono::steady_clock> start, end;
start = std::chrono::steady_clock::now();
// Некоторые вычисления...
end = std::chrono::steady_clock::now();

std::chrono::duration<double> elapsed_seconds = end-start;

elapsed_seconds.count(); // t количество секунд с типом `double`

Кортежи

Кортежи представляют собой наборы неоднородных значений фиксированного размера.

// `playerProfile` имеет тип `std::tuple<int, const char*, const char*>`.
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

Создает кортеж lvalue-элементов. Полезно для распаковки объектов std::pair и std::tuple.

std::string playerName;
std::tie(std::ignore, playerName, std::ignore) = std::make_tuple(91, "John Tavares", "NYI");

std::string yes, no;
std::tie(yes, no) = std::make_pair("yes", "no");

std::array

std::array – это контейнер, построенный поверх C-подобных массивов. Поддерживает общие контейнерные операции, такие как сортировка.

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_set, unordered_multisetunordered_mapunordered_multimap.

std::make_shared

std::make_shared рекомендуется использовать для создания экземпляров std::shared_ptr, так как эта функция позволяет:

  • избежать использования оператора new;
  • предотвратить дублирование кода при указании базового типа, который должен содержать указатель;
  • обеспечить безопасность при исключениях;
  • предотвратить повторное выделение памяти.

Модель памяти

C++11 стандартизирует модель памяти языка, обеспечивая поддержку библиотек для потоковых и атомарных операций.

Обновления C++: подборка изменений из трех стандартов языка

Шпаргалки

Удобные шпаргалки помогут лучше ориентироваться в языке и быстрее находить то, что нужно.

Первая, с базовыми конструкциями и понятиями, хорошо подойдет для начинающих программистов C++:

Шпаргалка по C++ для начинающих

Pdf-версия здесь.

Вторая будет удобна для опытных разработчиков:

Шпаргалка по C++

Pdf-версия здесь.

Полезные ресурсы

  • cppreference – примеры и документация новых функций.
  • C++ Rvalue References Explained – большое вводное руководства по ссылкам rvalue, правильной пересылке и семантике перемещения.
  • Поддержка стандартов clang и gcc.
  • C++ Weekly – коллекция видеоуроков по C++.

Оригинальная статья: modern-cpp-features.

Другие статьи по C++

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Go-разработчик
по итогам собеседования
Java Team Lead
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ