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

0
18216

Обновления 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++

РУБРИКИ В СТАТЬЕ

МЕРОПРИЯТИЯ

Комментарии 0

ВАКАНСИИ

PHP back-end developer
от 1500 USD до 2500 USD
Lead Go developer
Москва, от 230000 RUB до 270000 RUB
IOS developer
Москва, от 150000 RUB до 220000 RUB
Разработчик Java (микросервисы)
по итогам собеседования

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

BUG