Фото

21 фича современного C++


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

1. Разделители разрядов чисел

 

int no = 1'000'000;                      // визуальное разделение единиц, тысяч, миллионов и т.д.
long addr = 0xA000'EFFF;                 // визуальное разделение 32-битного адреса на
uint32_t binary = 0b0001'0010'0111'1111; // удобочитаемые сегменты
  • Раньше вам нужно было считать цифры или нули, но, начиная с C++14, вы можете сделать большие числа намного нагляднее.

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

  • Благодаря сгруппированным разрядам, ваш код станет немного выразительнее.

 

2. Псевдонимы типов

 

template <typename T>
using dyn_arr = std::vector<T>;
dyn_arr<int> nums; // эквивалентно std::vector<int>

using func_ptr = int (*)(int);
  • Семантически похоже на использование typedef, однако псевдонимы типов легче читаются и совместимы с шаблонами С++. Поблагодарите С++11.

 

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

 

using ull = unsigned long long;

constexpr ull operator"" _KB(ull no)
{
return no * 1024;
}

constexpr ull operator"" _MB(ull no)
{
return no * (1024_KB);
}

cout<<1_KB<<endl;
cout<<5_MB<<endl;
  • По большей части это будут какие-нибудь реальные единицы, такие как kb, mb, км, см, рубли, доллары, евро и т.д. Пользовательские литералы позволяют вам не определять функции, для выполнения преобразования единиц измерения во время выполнения, а работать с ним как с другими примитивными типами.

  • Очень удобно для единиц и измерения.

  • Благодаря добавлению constexpr вы можете добиться нулевого влияния на производительность во время выполнения, что мы увидим позже в этой статье, и более подробно вы можете почитать об этом в другой статье, которую я написал, — “Использование const и constexpr в С++”.

 

4. Унифицированная инициализация и инициализация нестатических членов

 

Раньше вам нужно было инициализировать поля их значениями по умолчанию в конструкторе или в списке инициализации. Но начиная с C++11 можно задавать обычным переменным-членам класса (тем, которые не объявлены с ключевым словом static) инициализирующее значение по умолчанию, как показано ниже:

class demo
{
private:
uint32_t m_var_1 = 0;
bool m_var_2 = false;
string m_var_3 = "";
float m_var_4 = 0.0;

public:
demo(uint32_t var_1, bool var_2, string var_3, float var_4)
: m_var_1(var_1),
m_var_2(var_2),
m_var_3(var_3),
m_var_4(var_4) {}
};

demo obj{123, true, "lol", 1.1};
  • Это особенно полезно, когда в качестве полей выступают сразу несколько вложенных объектов, определенных, как показано ниже:

class computer
{
private:
cpu_t m_cpu{2, 3.2_GHz};
ram_t m_ram{4_GB, RAM::TYPE::DDR4};
hard_disk_t m_ssd{1_TB, HDD::TYPE::SSD};

public:
// ...
};
  • В этом случае вам не нужно инициализировать их в списке инициализации. Вместо этого вы можете напрямую указать значение по умолчанию во время объявления.

class X
{
const static int m_var = 0;
};

// int X::m_var = 0; // не требуется для статических константных полей
  • Вы также можете инициализировать во время объявления const static члены класса, как показано выше.

 

5. std::initializer_list

 

std::pair<int, int> p = {1, 2};
std::tuple<int, int> t = {1, 2};
std::vector<int> v = {1, 2, 3, 4, 5};
std::set<int> s = {1, 2, 3, 4, 5};
std::list<int> l = {1, 2, 3, 4, 5};
std::deque<int> d = {1, 2, 3, 4, 5};

std::array<int, 5> a = {1, 2, 3, 4, 5};

// Не работает для адаптеров
// std::stack<int> s = {1, 2, 3, 4, 5};
// std::queue<int> q = {1, 2, 3, 4, 5};
// std::priority_queue<int> pq = {1, 2, 3, 4, 5};
  • Присваивайте значения контейнерам непосредственно с помощью списка инициализаторов, как это можно делать с C-массивами.

  • Это справедливо и для вложенных контейнеров. Скажите спасибо С++11.

 

6. auto & decltype

 

auto a = 3.14; // double
auto b = 1; // int
auto& c = b; // int&
auto g = new auto(123); // int*
auto x; // error -- `x` requires initializer
  • auto-типизированные переменные выводятся компилятором на основе типа их инициализатора.

  • Чрезвычайно полезно с точки зрения удобочитаемости, особенно для сложных типов:

// std::vector<int>::const_iterator cit = v.cbegin();
auto cit = v.cbegin(); // альтернатива

// std::shared_ptr<vector<uint32_t>> demo_ptr(new vector<uint32_t>(0);
auto demo_ptr = make_shared<vector<uint32_t>>(0); // альтернатива
  • Функции также могут выводить тип возвращаемого значения с помощью auto. В C++11 тип возвращаемого значения должен быть указан либо явно, либо с помощью decltype, например:

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
  • Приведенная выше форма определения возвращаемого типа называется trailing return type, т.е. -> return-type.

 

7. Циклы 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 }

 

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

 

  • C++11 добавляет в язык новые умные указатели: std::unique_ptrstd::shared_ptrstd::weak_ptr.

  • А std::auto_ptr устарел, и в конечном итоге удален в C++17.

std::unique_ptr<int> i_ptr1{new int{5}}; // Не рекомендуется 
auto i_ptr2 = std::make_unique<int>(5); // Так лучше

template <typename T>
struct demo
{
T m_var;

demo(T var) : m_var(var){};
};

auto i_ptr3 = std::make_shared<demo<uint32_t>>(4);
  • Гайдлайны ISO CPP рекомендуют избегать явных вызовов new и delete, выразив это в правиле “никаких голых new”.

 

9. nullptr

 

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

  • nullptr имеет тип std::nullptr_t и может быть неявно преобразован в типы непустых указателей, и в отличие от NULL, не конвертируем в целочисленные типы, за исключением bool.

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

 

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

 

enum class STATUS_t : uint32_t
{
PASS = 0,
FAIL,
HUNG
};

STATUS_t STATUS = STATUS_t::PASS;
STATUS - 1; // больше не валидно, начиная с C++11
  • Типобезопасные перечисления, которые решают множество проблем с C-перечислениями, включая неявные преобразования, арифметические операции, невозможность указать базовый тип, загрязнение области видимости и т.д.

 

11. Приведение типов

 

  • Приведение в стиле C изменяет только тип, не затрагивая сами данные. В то время как старый C++ имел небольшой уклон в типобезопасность, он предоставлял фичу указания оператора/функции преобразования типа. Но это было неявное преобразование типов. Начиная с C++11, функции преобразования типов теперь можно сделать явными с помощью спецификатора explicit следующим образом:

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

demo d;
if (d); // OK, вызывает demo::operator bool()
bool b_d = d; // ОШИБКА: не может преобразовать 'demo' в 'bool' во время инициализации
bool b_d = static_cast<bool>(d); // OK, явное преобразование, вы знаете, что делаете

 

 

12. Move-семантика

 

  • Когда объект будет уничтожен или не будет более использоваться после выполнения выражения, целесообразнее переместить (move) ресурс, а не копировать его.

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

  • Рассмотрим следующую функцию, меняющую местами два значения:

template <class T>
swap(T& a, T& b) {
T tmp(a); // теперь у нас есть две копии a
a = b; // теперь у нас есть две копии b (+ отброшена копия a)
b = tmp; // теперь у нас есть две копии tmp (+ отброшена копия b)
}
  • Использование move позволяет вам напрямую обменивать ресурсы вместо их копирования:

template <class T>
swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
  • А теперь представьте, что происходит, когда Т это, скажем, vector<int> размера n. И n достаточно велико.

  • В первой версии вы читаете и записываете 3*n элементов, во второй версии вы в по сути читаете и записываете только 3 указателя на буферы векторов плюс 3 размера буферов.

  • Конечно, класс Т должен знать, как ему перемещаться; ваш класс должен иметь оператор присваивания перемещением и конструктор перемещения для класса Т, чтобы это работало.

  • Эта фича даст вам значительный прирост в производительности — именно то, поэтому люди используют C++ (т.е., чтобы выжать последние 2-3 капли скорости).

 

13. Универсальные ссылки

 

  • В официальной терминологии известные как forwarding references (передаваемые ссылки). Универсальная ссылка объявляется с помощью синтаксиса Т&&, где Т является шаблонным параметром типа, или с помощью auto&&. Они в свою очередь служат фундаментом для двух других крупных фич:

    • move-семантика

    • И perfect forwarding, возможность передавать аргументы, которые являются либо lvalue, либо rvalue.

Универсальные ссылки позволяют ссылаться на привязку либо к lvalue, либо к rvalue в зависимости от типа. Универсальные ссылки следуют правилам свертывания ссылок:

  1. T& & становится  T&  

  2. T& && становится T&

  3. T&& & становится T&

  4. T&& && становится T&&

Вывод шаблонного параметра типа с lvalue ​​и rvalue:

// Начиная с C++14 и далее:
void f(auto&& t) {
// ...
}

// Начиная с C++11 и далее:
template <typename T>
void f(T&& t) {
// ...
}

int x = 0;
f(0); // выводится как f(int&&)
f(x); // выводится как f(int&)

int& y = x;
f(y); // выводится как f(int& &&) => f(int&)

int&& z = 0; // ПРИМЕЧАНИЕ: z — это lvalue типа int&amp;&amp;.
f(z); // выводится как f(int&& &) => f(int&)
f(std::move(z)); // выводится как f(int&& &&) => f(int&&)

 

 

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

 

void print() {}

template <typename First, typename... Rest>
void print(const First &first, Rest &&... args)
{
std::cout << first << std::endl;
print(args...);
}

print(1, "lol", 1.1);
  • Синтаксис ... создает пакет параметров или расширяет уже существующий. Шаблонный пакет параметров — это шаблонный параметр, который принимает ноль или более аргументов-шаблонов (нетипизированных объектов, типов или шаблонов). Шаблон С++ с хотя бы одним пакетом параметров называется вариативный шаблоном с переменным количеством аргументов (variadic template).

 

15. constexpr

 

constexpr uint32_t fibonacci(uint32_t i)
{
return (i <= 1u) ? i : (fibonacci(i - 1) + fibonacci(i - 2));
}

constexpr auto fib_5th_term = fibonacci(6); // равноценно auto fib_5th_term = 8
  • Константные выражения — это выражения, вычисляемые компилятором во время компиляции. В приведенном выше примере функция fibonacci выполняется/вычисляется компилятором во время компиляции, и будет заменена на результат в вызове места.

 

16. Удаленные и дефолтные функции

 

struct demo
{
demo() = default;
};

demo d;
  • У вас вполне закономерно может возникнуть вопрос, зачем вам писать 8+ букв (т.е. = default;), когда можно просто использовать {}, т.е. пустой конструктор? Никто вас не останавливает. Но подумай о конструкторе копирования, операторе копирования присваиванием, и т.д.

  • Пустой конструктор копирования, например, не то же самое, что конструктор копирования по умолчанию (который будет выполнять почленную копию всех членов).

Вы можете ограничить определенную операцию или способ инстанцирования объекта, просто удалив соответствующий метод, как показано ниже:

class demo
{
int m_x;

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

demo obj1{123};
demo obj2 = obj1; // ОШИБКА -- вызов удаленного конструктора копирования
obj2 = obj1; // ОШИБКА -- оператор = удален

В старом С++ вы должны были сделать его приватным. Но теперь в вашем распоряжении есть директива компилятора delete.

 

 

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

 

struct demo
{
int m_var;
demo(int var) : m_var(var) {}
demo() : demo(0) {}
};

demo d;
  • В старом C++ вам нужно создавать функцию-член для  инициализации и вызывать ее из всех конструкторов для достижения универсально инициализации.

  • Но начиная с C++11 конструкторы теперь могут вызывать другие конструкторы из того же класса с помощью списка инициализаторов.

 

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

 

auto generator = [i = 0]() mutable { return ++i; };
cout << generator() << endl; // 1
cout << generator() << endl; // 2
cout << generator() << endl; // 3
  • Я думаю, что эта фича не нуждается в представлении и является фаворитом среди других фич.

  • Теперь вы можете объявлять функции где угодно. И это не будет стоить вам никаких дополнительных накладных расходов. 

 

19. Операторы ветвления с инициализатором

 

  • В более ранних версиях C++ инициализатор либо объявлялся перед оператором и просачивался во внешнюю область видимости, либо использовалась явная область видимости.

  • В C++17 появилась новая форма if/switch, которую можно записать более компактно, а улучшенный контроль области видимости делает некоторые ранее подверженные ошибкам конструкции немного более надежными:

switch (auto STATUS = window.status()) // Объявляем объект прямо в операторе ветвления
{
case PASS:// делаем что-то
break;
case FAIL:// делаем что-то
break;
}
  • Как это работает

{
auto STATUS = window.status();
switch (STATUS)
{
case PASS: // делаем что-то
break;
case FAIL: // делаем что-то
break;
}
}

 

20. std::tuple

 

auto employee = std::make_tuple(32, " Vishal Chovatiya", "Bangalore");
cout << std::get<0>(employee) << endl; // 32
cout << std::get<1>(employee) << endl; // "Vishal Chovatiya"
cout << std::get<2>(employee) << endl; // "Bangalore"
  • Кортежи представляют собой набор разнородных значений фиксированного размера. Доступ к элементам std::tuple производится с помощью std::tie или std::get.

  • Вы также можете выхватывать произвольные и разнородные возвращаемые значения следующим образом:

auto get_employee_detail()
{
// делаем что-нибудь . . .
return std::make_tuple(32, " Vishal Chovatiya", "Bangalore");
}

string name;
std::tie(std::ignore, name, std::ignore) = get_employee_detail();
  • Используйте std::ignore в качестве плейсхолдера для игнорируемых значений. В С++ 17, вместо этого следует использовать структурированные привязки.

 

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

 

std::pair<std::string, int> user = {"M", 25}; // раньше
std::pair user = {"M", 25}; // C++17

std::tuple<std::string, std::string, int> user("M", "Chy", 25); // раньше
std::tuple user2("M", "Chy", 25); // выведение в действии!
  • Автоматическое выведение аргументов шаблона очень похоже на то, как это делается для функций, но теперь также включает и конструкторы классов.

 

Пара слов в заключение 

 

Здесь мы только слегка коснулись огромного набора новых фич и возможности их применения. В современном C++ можно найти еще очень много чего, но тем не менее вы можете считать этот набор хорошей отправной точкой. Современный C++ расширяется не только с точки зрения синтаксиса, но также добавляется гораздо больше других функций, таких как неупорядоченные контейнеры, потоки, регулярное выражение, Chrono, генератор/распределитель случайных чисел, обработка исключений и множество новых алгоритмов STL (например, all_of()any_of()none_of(), и т.д).

Да пребудет с вами C++!



Фото

Удивительные возможности современного C++


Было время, когда С++ не хватало динамизма, и увлечься этим языком было трудно. Но всё изменилось, когда было принято решение развить стандарт C++. С 2011 года язык стал более динамичным и постоянно развивается. В статье мы рассмотрим некоторые интересные функциональные возможности языка.

Ключевое слово auto

Когда в 11 версии C++ только появилось auto, жизнь стала намного легче.

Идея auto состояла в том, чтобы заставить компилятор C++ определять тип ваших данных во время компиляции, вместо того чтобы заставлять вас каждый раз объявлять тип. Это было удобно, если у вас были типы данных вроде map<string, vector <pair <int, int>>> ?

 

auto an_int = 26;              // при компиляции тип выводится в int
auto a_bool = false;        // в bool
auto a_float = 26.04f;     // в float
auto ptr = &a_float;        // и даже в указатель
auto data;                        // а можно ли так? Вообще-то нельзя.

 

Посмотрите на строку номер 5. Вы не можете объявить что-либо без инициализатора. Строка 5 не сообщает компилятору, каким может быть тип данных.

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

 

auto merge(auto a, auto b)     // Тип параметров и возвращаемых данных тоже может быть auto!
{
std::vector c = do_something(a, b);
return c;
}
std::vector<int> a = { ... };     // какие-то данные
std::vector<int> b = { ... };    // какие-то данные
auto c = merge(a, b);           // тип определяется возвращаемой информацией!
 
 

В строках 7 и 8 была использована инициализация в скобках. Эта функция также была добавлена в 11 версии C++.

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

Теперь встаёт хороший вопрос, что произойдёт, если мы напишем auto a = {1, 2, 3}? Это ошибка компиляции? Это вектор?

На самом деле, в 11 версии C++ был представлен std::initializer_list<type>. Инициализированный список в скобках будет считаться легковесным контейнером, если объявлен как auto.

И как упоминалось ранее, определять типы объектов компилятором полезно, когда у вас есть сложные структуры данных:

 

void populate(auto &data) {                                    // видите!
data.insert({"a", {1, 4}});
data.insert({"b", {3, 1}});
data.insert({"c", {2, 3}});
}
 
auto merge(auto data, auto upcoming_data) {     // и не надо писать длинный идентификатор снова
auto result = data;
for (auto it: upcoming_data) {
result.insert(it);
}
return result;
}
 
int main() {
std::map<std::string, std::pair<int, int>> data;
populate(data);
 
std::map<std::string, std::pait<int, int>> upcoming_data;
upcoming_data.insert({"d", {5, 3}});
 
auto final_data = merge(data, upcoming_data);
for (auto itr: final_data) {
auto [v1, v2] = itr.second;                                         // про структурное связывание будет ниже
std::cout << itr.first << " " << v1 << " " << v2 << std::endl;
}
return 0;
}
 

Не забудьте проверить строку 25! Выражение auto [v1, v2] = itr.second — новая функция в 17 версии C++. Это называется структурным связыванием. В предыдущих версиях приходилось извлекать каждую переменную отдельно. Но структурное связывание сделало этот процесс более удобным.

Более того, если вы хотите получить данные, используя ссылку, то просто добавьте символ — auto &[v1, v2] = itr.second.

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

В 11 версии C++ появились лямбда-выражения. Это что-то вроде анонимных функций в JavaScript. Они являются безымянными функциональными объектами и захватывают переменные в различных областях на основе некоторого краткого синтаксиса. Они также могут быть присвоены переменным.

Лямбды будут полезны, если вам нужно сделать в коде быстрое и небольшое изменение, и вы не хотите писать для этого отдельную функцию. Другое довольно распространённое использование функции — сравнение.

 

std::vector<std::pair::<int, int>> data = {{1, 3}, {7, 6}, {12, 4}};    // обратите внимание на скобочную инициализацию
std::sort(begin(data), end(data), [ ](auto a, auto b) {                   // auto!
    return a.second < b.second;
});

 

Приведённый выше пример может многое сказать.

Во-первых, обратите внимание, как фигурные скобки упрощают вам жизнь. Затем следуют универсальные begin()end(), которые тоже были добавлены в 11 версии. После идёт лямбда-выражение в качестве компаратора ваших данных. Параметры лямбда-выражения объявлены с помощью auto, что было добавлено в 14 версии С++. До этого auto нельзя было использовать в качестве параметров функции.

Обратите внимание, мы начинаем лямбда-выражение с квадратных скобок [ ]. Они определяют область действия лямбды — сколько у неё полномочий над локальными переменными и объектами.

Как определено в этом потрясающем репозитории по современному C++:

  • [ ] — ничего не захватывает. Таким образом, вы не можете использовать любую локальную переменную внешней области видимости в лямбда-выражении. Вы можете использовать только параметры.
  • [=] — захватывает локальные объекты (локальные переменные, параметры) в области видимости по значению. Вы можете использовать, но не изменять их.
  • [&] — захватывает локальные объекты (локальные переменные, параметры) в области видимости по ссылке. Вы можете изменить их, как в примере, приведённом ниже.
  • [this] — захватывает этот указатель по значению.
  • [a, &b] — захватывает объект a по значению, объект b по ссылке.

Так что, если внутри лямбда-функции вы хотите преобразовать данные в какой-то другой формат, вы можете применить её, воспользовавшись преимуществами области видимости. Например:

 

std::vector<int> data = {2, 4, 4, 1, 1, 3, 9};
int factor = 7;
for_each(begin(data), end(data), [&factor](int &val) {          // захват factor по ссылке
val = val * factor;
factor--;                             // это будет работать, потому что переменная находится в области видимости лямбды
});
for(int val: data) {
std::cout << val << ' ';      // 14 24 20 4 3 6 9
}
 

В приведённом выше примере, если вы захватили локальные переменные по значению ([factor]) в лямбда-выражении, то вы не можете изменить factor в 5 строке. Вы просто не имеете права делать это. Не злоупотребляйте своими правами!

Наконец, обратите внимание, что мы берём переменную val в качестве ссылки. Это гарантирует, что любое изменение внутри лямбда-функции фактически изменяет vector.

Инициализатор в if и switch

Вам точно понравится эта возможность в С++ 17.

 

std::set<int> input = {1, 5, 3, 6};
if(auto it = input.find(7); it == input.end()) {         // первая часть - инициализация, вторая - условие
std::cout << 7 << " not found!" << std::endl;
}
else {
             // it тоже попадает в область видимости else!
std::cout << 7 << " is there!" << std::endl;
}
 
Очевидно, теперь вы можете выполнять инициализацию переменных и проверять условие сразу внутри блоков if или switch. Это поможет сделать код лаконичным и чистым. Общая форма:
 
if (init-statement(x); condition(x)) {
    // какой-то код

else {    // else тоже имеет переменную x в области видимости
    // какой-то другой код
}
 

Компиляция и constexpr

Скажем, у вас есть какое-то выражение для оценки, и его значение не изменится после инициализации. Вы можете предварительно рассчитать значение, а затем использовать его в качестве макроса. Или, как предложил C++ 11, можно использовать constexpr.

Программисты стремятся максимально сократить время выполнения программ. Поэтому если некоторые операции можно отдать на выполнение компилятору, это стоит сделать.

 

constexpr double fib(int n) {     // функция объявлена с помощью constexpr
if(n == 1) return 1;
return fib(n-1) * n;
}
int main()
{
const long long bigval = fib(20);
std::cout << bigval << std::endl;
return 0;
}
 

Приведённый выше код — распространённый пример использования constexpr.

Поскольку мы объявили функцию вычисления Фибоначчи как constexpr, компилятор может предварительно вычислить fib(20) во время компиляции. Так что после неё он может заменить строку с

const long long bigval = fib (20);

на

const long long bigval = 2432902008176640000;

 

Обратите внимание, что переданный аргумент является константным значением. Важный момент: в функциях, объявленных constexpr, передаваемые аргументы также должны быть constexpr или const. В противном случае они будут вести себя как обычные функции, и во время компиляции предварительный расчёт выполняться не будет.

Переменные также могут быть constexpr. В этом случае, как вы можете догадаться, эти переменные должны вычисляться во время компиляции. Иначе вы получите ошибку компиляции.

Интересно, что позже в C++ 17 были представлены constexpr-if и constexpr-lambda.

Кортежи

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

auto user_info = std::make_tuple("M", "Chowdhury", 25); // используем auto, чтобы уменьшить описание типов

                                                                                                    // чтобы получить доступ к данным
std::get<0>(user_info);
std::get<1>(user_info);
std::get<2>(user_info);

// в 11 версии С++ мы использовали tie, чтобы сделать связывание

std::string first_name, last_name, age;
std::tie(first_name, last_name, age) = user_info;

// но в 17 версии стало гораздо удобнее
auto [first_name, last_name, age] = user_info;

 Иногда удобнее использовать std::array вместо кортежа. Такой массив подобен обычному массиву в C вместе с несколькими функциями стандартной библиотеки C++. Эта структура данных была добавлена в 11 версии C++.

 

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

Очень подробное название для функции. Идея состоит в том, что с 17 версии типы шаблонных параметров будут выводиться и для стандартных шаблонных классов. Ранее это поддерживалось только для функций.

 

std::pair<std::string, int> user = {"M", 25};         // раньше
std::pair user = {"M", 25};                                     // C++ 17

 

В этом примере для первого элемента кортежа будет выведен тип const char *, а не std::string.

Выводимый тип задаётся неявно. Это становится ещё удобнее для кортежей.

 

// раньше
std::tuple<std::string, std::string, int> user ("M", "Chy", 25); 
// C++ 17
std::tuple user2("M", "Chy", 25);

 

Эта функция не имеет никакого смысла, если вы слабо знакомы с шаблонами в C++.

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

Указатели могут быть адскими.

Из-за свободы, которую предоставляют такие языки, как C++, иногда становится очень легко выстрелить себе в ногу. И во многих случаях именно указатели ответственны за вред, нанесённый компьютеру.

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

 

Источник:  tproger.ru

 



1