Обновления 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_multiset
, unordered_map
, unordered_multimap
.
std::make_shared
std::make_shared
рекомендуется использовать для создания экземпляров std::shared_ptr
, так как эта функция позволяет:
- избежать использования оператора
new
; - предотвратить дублирование кода при указании базового типа, который должен содержать указатель;
- обеспечить безопасность при исключениях;
- предотвратить повторное выделение памяти.
Модель памяти
C++11 стандартизирует модель памяти языка, обеспечивая поддержку библиотек для потоковых и атомарных операций.
Шпаргалки
Удобные шпаргалки помогут лучше ориентироваться в языке и быстрее находить то, что нужно.
Первая, с базовыми конструкциями и понятиями, хорошо подойдет для начинающих программистов C++:
Pdf-версия здесь.
Вторая будет удобна для опытных разработчиков:
Pdf-версия здесь.
Полезные ресурсы
- cppreference – примеры и документация новых функций.
- C++ Rvalue References Explained – большое вводное руководства по ссылкам rvalue, правильной пересылке и семантике перемещения.
- Поддержка стандартов clang и gcc.
- C++ Weekly – коллекция видеоуроков по C++.
Оригинальная статья: modern-cpp-features.