Обновления 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.
Комментарии