image Привет, Хаброжители!

Программисты с опытом работы на других языках высокого уровня смогут на практике освоить современный С++ и «большую четверку» его новых возможностей: диапазоны, концепты, модули и корутины.

200+ практических примеров реального исходного кода позволят быстро овладеть идиомами современного С++, используя популярные компиляторы: Visual C++®, GNU® g++, Apple® Xcode® и LLVM®/Clang. Знание базы позволит перейти к контейнерам стандартной библиотеки С++ array и vector; функциональному программированию с диапазонами и представлениями C++20; строкам, файлам и регулярным выражениям; объектно-ориентированному программированию с классами, наследованием, динамическим и статическим полиморфизмом; перегрузке операторов, семантике копирования и перемещения, RAII и умным указателям; исключениям и ожидаемым в С++23 контрактам; контейнерам, итераторам и алгоритмам стандартной библиотеки; шаблонам, концептам С++20 и метапрограммированию; модулям С++ 20 и технологии разработки больших программ; конкурентности, параллелизму, параллельным алгоритмам стандартной библиотеки С++17 и С++20 и корутинам С++20.
Кому пригодится эта книга
Книга «C++20 для программистов. Метод готовых объектов» рассчитана на следующие целевые аудитории:
  • разработчики программ на C++, которые хотят изучить новейшие возможности C++20 по подробному учебнику, написанному в профессиональном стиле;
  • разработчики программ на других языках, которые готовятся к выполнению проекта на языке C++ и хотят изучить его актуальную версию;
  • разработчики, которые изучали C++ в вузе или работали с ним много лет назад, а теперь хотят освежить свои знания в контексте C++20;
  • профессиональные преподаватели C++, составляющие курсы по C++20.

Создание класса myArray с перегрузкой операторов


Проектирование классов — это интересная, творческая и сложная работа, цель которой — создание практичного и полезного кода. В этом разделе мы будем называть словом «массив» традиционный массив в стиле C (глава 7). Такие массивы, основанные на указателях, имеют много проблем, в том числе:
  • C++ не проверяет выход индекса массива за границы. Программа может легко «вылететь» за границы массива, что почти наверняка приведет к фатальной ошибке во время выполнения, если вы забудете предусмотреть проверку индекса в своем коде.
  • Массивы с размерностью n должны использовать значения индекса в диапазоне от 0 до n – 1. Альтернативные диапазоны индексов не допускаются.
  • Вы не можете вводить (записывать) массив оператором извлечения из потока (>>) и выводить (считывать) массив оператором вставки в поток (<<). Вам приходится записывать или считывать каждый элемент массива по очереди.
  • Два массива нельзя сопоставить операторами равенства или сравнения. Имя массива — это просто указатель на начало массива в памяти. Два массива всегда будут находиться в разных ячейках памяти.
  • Когда вы передаете массив в функцию, обрабатывающую массив любого размера, вы должны передать еще и дополнительный аргумент: размер массива. Как вы видели в разделе 7.10, объекты span (C++20) помогают решить эту проблему.
  • Нельзя присваивать один массив другому операторами присваивания.
В C++ вы можете реализовать работу с массивами более надежно, используя классы и перегруженные операторы, как в шаблонах классов стандартной библиотеки C++ array и vector. В этом разделе мы разработаем пользовательский класс myArray, который тоже значительно надежнее традиционных массивов. Внутри класса myArray будет использоваться умный указатель unique_ptr для управления традиционным массивом с целочисленными значениями в выделенной памяти.

Перечислим ценные возможности класса myArray:
  • Объект класса myArray выполняет проверку границ при обращении к нему через оператор индекса ([]), чтобы индексы гарантированно находились в пределах границ массива. В противном случае объект myArray выдает исключение out_of_bounds стандартной библиотеки.
  • Весь объект myArray можно вводить (записывать) и выводить (считывать) перегруженными операторами извлечения из потока (>>) и вставки в поток (<<) соответственно, без необходимости циклов в клиентском коде.
  • Объекты myArray можно сравнивать друг с другом операторами равенства == и !=. Класс можно будет легко расширить, добавив поддержку операторов сравнения.
  • Объект myArray знает свой размер, что упрощает его передачу функциям.
  • Объекты myArray можно присваивать друг другу оператором присваивания.
  • Объект myArray можно преобразовывать в значение false или true, показывающее, является ли он пустым или содержит элементы.
  • Объект myArray предоставляет операторы префиксного и постфиксного инкремента (++), прибавляющие 1 к каждому элементу. Мы сможем легко добавить поддержку операторов префиксного и постфиксного декремента (--).
  • Объект myArray предоставляет составной оператор присваивания (+=), прибавляющий указанное значение к каждому элементу. Класс можно будет легко расширить, добавив поддержку других составных операторов присваивания: -=, *=, /= и %=.
Класс myArray будет содержать пять специальных функций, а также умный указатель unique_ptr для управления динамически выделяемой памятью. В этом примере мы используем идиому RAII («получение ресурсов есть инициализация») для управления динамическими ресурсами памяти. Конструкторы класса будут выделять память по мере инициализации объектов myArray. Деструкторы класса будут предотвращать утечки памяти, освобождая память, когда объекты выходят из области видимости. Наш класс myArray не предназначен для замены стандартных шаблонов array и vector или для имитации их возможностей. В нем демонстрируются ключевые функции языка C++ и функции библиотеки, которые пригодятся вам при создании ваших собственных классов.

Специальные функции класса


Каждый пользовательский класс может содержать пять специальных функций, которые мы определим в классе myArray:
  • копирующий конструктор (copy constructor);
  • оператор копирующего присваивания (copy assignment operator);
  • перемещающий конструктор (move constructor);
  • оператор перемещающего присваивания (move assignment operator);
  • деструктор.
Копирующий конструктор и оператор копирующего присваивания реализуют семантику копирования (copy semantics) класса, то есть такой процесс копирования объекта, когда он передается в функцию по значению, возвращается из функции по значению или присваивается другому объекту myArray. Перемещающий конструктор и оператор перемещающего присваивания реализуют семантику перемещения (move semantics) класса, которая устраняет ненужное копирование объектов, когда в копиях нет необходимости. Мы будем обсуждать детали этих специальных функций классов по мере их реализации в нашем классе.

Работа с классом myArray


Иллюстрации 11.3–11.5 демонстрируют работу класса myArray и его богатый набор перегруженных операторов. Код, показанный на илл. 11.3, тестирует различные возможности класса myArray. Определение класса показано на илл. 11.4, а определения его функций — на илл. 11.5. Мы разделили код и выходные данные на небольшие фрагменты для удобства обсуждения. Для наглядности многие функции класса, в том числе все его специальные функции, выводят сообщения, показанные в выходных данных.

Функция getArrayByValue

В этой программе мы будем вызывать функцию getArrayByValue (илл. 11.3, строки 10–13), чтобы создать локальный объект myArray через конструктор класса myArray, получающий список инициализаторов. Функция getArrayByValue возвращает этот локальный объект по значению.
___________________________________________________________________________________
1   // fig11_03.cpp
2   // Тестирование класса MyArray
3   #include <fmt/format.h>
4   #include <iostream>
5   #include <stdexcept>
6   #include <utility> // Для std::move
7   #include "MyArray.h"
8
9   // Функция для возврата MyArray по значению
10  MyArray getArrayByValue() {
11   MyArray localInts{10, 20, 30}; // Создаем MyArray с тремя элементами
12   return localInts; // Возврат по значению создает rvalue
13 }
14
___________________________________________________________________________________
Илл. 11.3 | Тестирование класса MyArray

Создание объектов myArray, отображение их размера и содержимого

В строках 16–17 создаются объекты: ints1 с семью элементами и ints2 с десятью элементами. Каждый из них вызывает конструктор класса myArray, который получает количество элементов и инициализирует элементы нулевым значением. В строках 20–21 отображается размер объекта ints1 и выводится его содержимое с помощью перегруженного оператора вставки в поток (<<). В строках 24–25 выполняются аналогичные действия с объектом ints2.
___________________________________________________________________________________
15 int main() {
16    MyArray ints1(7); // MyArray с 7 элементами. Внимание: круглые скобки!
17    MyArray ints2(10); // MyArray с 10 элементами. Внимание: круглые скобки!
18
19    // Выводим размер и содержимое объекта ints1
20    std::cout << fmt::format("\nints1 size: {}\ncontents: ", ints1.size())
21       << ints1; // Используем перегруженный оператор <<
22
23    // Выводим размер и содержимое объекта ints2
24    std::cout << fmt::format("\nints2 size: {}\ncontents: ", ints2.size())
25       << ints2; // Используем перегруженный оператор <<
26

MyArray(size_t) constructor
MyArray(size_t) constructor

ints1 size: 7
contents: {0, 0, 0, 0, 0, 0, 0}

ints2 size: 10
contents: {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

Круглые скобки вместо фигурных для вызова конструктора

До сих пор мы использовали скобочную инициализацию {} для передачи аргументов конструкторам. В строках 16–17 мы используем круглые скобки () для вызова конструктора myArray, получающего размер объекта. Мы делаем это потому, что наш класс myArray, подобно классам стандартной библиотеки array и vector, поддерживает конструирование объекта по списку инициализаторов, содержащему значения элементов. Например, когда компилятор видит инструкцию:

MyArray ints1{7};

он вызывает конструктор, принимающий список целых чисел, а не конструктор с одним аргументом, получающий размер.

Перегруженный оператор извлечения из потока для заполнения объекта myArray

Далее в строке 28 пользователю предлагается ввести 17 целых чисел. В строке 29 используется перегруженный оператор извлечения из потока (>>), чтобы получить первые семь значений для объекта ints1 и еще десять значений для объекта ints2 (напомним, что каждый объект myArray знает свой размер). В строке 31 отображается обновленное содержимое каждого объекта myArray с помощью перегруженного оператора вставки в поток (<<).
___________________________________________________________________________________
27   // Считываем данные для объектов ints1 и ints2, выводим их содержимое
28   std::cout << "\n\nEnter 17 integers: ";
29   std::cin >> ints1 >> ints2; // Используем перегруженный оператор >>
30
31   std::cout << "\nints1: " << ints1 << "\nints2: " << ints2;
32

Enter 17 integers: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

ints1: {1, 2, 3, 4, 5, 6, 7}
ints2: {8, 9, 10, 11, 12, 13, 14, 15, 16, 17}

Перегруженный оператор неравенства (!=)

В строке 36 для проверки работы перегруженного оператора неравенства (!=) вычисляется условие:

ints1 != ints2

Выходные данные показывают, что объекты myArray не равны. Два объекта будут равны, если они имеют одинаковое количество элементов и соответствующие значения всех элементов идентичны. Как вы скоро увидите, мы определим только перегруженный оператор ==. В C++20 компилятор автоматически генерирует перегруженный оператор !=, если вы предоставили оператор == для вашего типа. Оператор != просто возвращает значение, противоположное оператору ==.
___________________________________________________________________________________
33   // Перегруженный оператор неравенства (!=)
34   std::cout << "\n\nEvaluating: ints1 != ints2\n";
35
36   if (ints1 != ints2) {
37      std::cout << "ints1 and ints2 are not equal\n\n";
38   }
39

Evaluating: ints1 != ints2
ints1 and ints2 are not equal

Инициализация нового объекта myArray копией существующего объекта myArray

В строке 41 создается объект ints3 и инициализируется копией объекта ints1. Эта инструкция вызывает копирующий конструктор класса myArray для копирования элементов ints1 в ints3. Копирующий конструктор вызывается каждый раз, когда требуется копия объекта, например:
  • при передаче объекта в функцию по значению;
  • при возврате объекта из функции по значению;
  • при инициализации объекта копией другого объекта того же класса.
В строках 44–45 отображаются размер и содержимое объекта ints3, что подтверждает корректную инициализацию ints3 копирующим конструктором.
___________________________________________________________________________________
40   // Создаем объект ints3 как копию объекта ints1
41   MyArray ints3{ints1}; // Вызываем копирующий конструктор
42
43   // Выводим размер и содержимое объекта ints3
44   std::cout << fmt::format("\nints3 size: {}\ncontents: ", ints3.size())
45      << ints3;
46

MyArray copy constructor

ints3 size: 7
contents: {1, 2, 3, 4, 5, 6, 7}

Когда пользовательский класс, в нашем случае myArray, содержит и копирующий, и перемещающий конструкторы, компилятор выбирает нужный конструктор в зависимости от контекста. В строке 41 компилятор выбирает копирующий конструктор класса myArray, потому что переменные, такие как ints1, являются lvalue. Как вы скоро увидите, перемещающий конструктор получает ссылку rvalue, являющуюся частью семантики перемещения (C++11). Ссылка rvalue может не ссылаться на значение lvalue.
Копирующий конструктор также может быть вызван инструкцией:

MyArray ints3 = ints1;

В определении объекта знак равенства не означает присваивание. Он вызывает копирующий конструктор с одним аргументом, передавая в качестве аргумента значение справа от символа =.

Перегруженный оператор копирующего присваивания (=)

В строке 49 объект ints2 присваивается объекту ints1 для тестирования перегруженного оператора копирующего присваивания (=). Традиционные массивы не поддерживают эту операцию. Имя массива не является изменяемым значением lvalue, поэтому попытка присвоить имя массива вызывает ошибку компиляции. В строке 51 отображается содержимое двух объектов, что подтверждает их идентичность. Объект ints1 изначально содержал семь целых чисел, но перегруженный оператор изменяет размер массива, чтобы вместить копию объекта ints2 из десяти элементов. Как и в случае с копирующими и перемещающими конструкторами, если класс содержит и оператор копирующего присваивания, и оператор перемещающего присваивания, компилятор делает выбор между ними, основываясь на аргументах. В этом случае объект ints2 является переменной и, следовательно, значением lvalue, поэтому вызывается оператор копирующего присваивания. Обратите внимание на выходные данные: инструкция в строке 49 вызывает копирующий конструктор и деструктор — вы поймете почему, когда мы рассмотрим реализацию оператора присваивания в разделе 11.6.6.
___________________________________________________________________________________
47   // Перегруженный оператор присваивания (=)
48   std::cout << "\n\nAssigning ints2 to ints1:\n";
49   ints1 = ints2; // Внимание: до присваивания ints1 был меньше, чем ints2
50
51   std::cout << "\nints1: " << ints1 << "\nints2: " << ints2;
52

Assigning ints2 to ints1:
MyArray copy assignment operator
MyArray copy constructor
MyArray destructor

ints1: {8, 9, 10, 11, 12, 13, 14, 15, 16, 17}
ints2: {8, 9, 10, 11, 12, 13, 14, 15, 16, 17}

Перегруженный оператор равенства (==)

В строке 56 объекты inst1 и ints2 сравниваются перегруженным оператором равенства (==), что подтверждает их идентичность после присваивания в строке 49.
___________________________________________________________________________________
53   // Перегруженный оператор равенства (==)
54   std::cout << "\n\nEvaluating: ints1 == ints2\n";
55
56   if (ints1 == ints2) {
57      std::cout << "ints1 and ints2 are equal\n\n";
58   }
59

Evaluating: ints1 == ints2
ints1 and ints2 are equal

Перегруженный оператор индекса ([ ])

В строке 61 перегруженный оператор индекса ([]) используется для доступа к элементу ints1[5], который находится в пределах границ объекта ints1. Имя с индексом используется для получения значения, хранящегося в ints1[5]. В строке 65 ints1[5] используется в левой части присваивания в качестве изменяемого значения lvalue, чтобы присвоить элементу с индексом 5 объекта ints1 новое значение 1000. Далее мы увидим, что оператор индекса возвращает ссылку в качестве изменяемого значения lvalue после подтверждения допустимости указанного индекса. В строке 71 мы пытаемся присвоить новое значение 1000 несуществующему элементу ints1[15]. Этот индекс находится за пределами границ объекта int1, поэтому перегруженный оператор индекса выдает исключение out_of_range. В строках 73–75 исключение перехватывается и вызывается его функция what, выводящая сообщение об ошибке.
___________________________________________________________________________________
60   // Используем перегруженный оператор индекса для создания значения rvalue
61   std::cout << fmt::format("ints1[5] is {}\n\n", ints1[5]);
62
63   // Используем перегруженный оператор индекса для создания значения lvalue
64   std::cout << "Assigning 1000 to ints1[5]\n";
65   ints1[5] = 1000;
66   std::cout << "ints1: " << ints1;
67
68   // Пытаемся получить доступ к несуществующему элементу
69   try {
70      std::cout << "\n\nAttempt to assign 1000 to ints1[15]\n";
71      ints1[15] = 1000; // Ошибка: недопустимый индекс!
72   }
73   catch (const std::out_of_range& ex) {
74      std::cout << fmt::format("An exception occurred: {}\n", ex.what());
75   }
76

ints1[5] is 13

Assigning 1000 to ints1[5]
ints1: {8, 9, 10, 11, 12, 1000, 14, 15, 16, 17}

Attempt to assign 1000 to ints1[15]
An exception occurred: Index out of range

Оператор индекса [] нужен не только для работы с массивами. Его также можно использовать, например, для выбора элементов из других контейнеров, поддерживающих коллекции элементов, таких как string (коллекции символов) и map (коллекции пар «ключ — значение», которые мы рассмотрим в главе 13). Кроме того, когда определены перегруженные функции этого оператора, индексы не обязательно должны быть целыми числами. В главе 13 мы рассмотрим класс map стандартной библиотеки, который допускает индексы других типов, например string.

Создание и инициализация объекта ints4 функцией getArrayByValue

В строке 80 объект ints4 инициализируется результатом вызова функции getArrayByValue (строки 10–13), которая создает локальный объект класса myArray (содержащий три целых числа 10, 20 и 30) и возвращает объект по значению. В строках 82–83 отображаются размер и содержимое нового объекта ints4.
___________________________________________________________________________________
77   // Инициализируем ints4 содержимым объекта MyArray, возвращенного
78   // функцией getArrayByValue; выводим размер и содержимое
79   std::cout << "\nInitialize ints4 with temporary MyArray object\n";
80   MyArray ints4{getArrayByValue()};
81
82   std::cout << fmt::format("\nints4 size: {}\ncontents: ", ints4.size())
83      << ints4;
84

Initialize ints4 with temporary MyArray object
MyArray(initializer_list) constructor

ints4 size: 3
contents: {10, 20, 30}

Оптимизация именованного возвращаемого значения (NRVO)

Напомним из определения функции getArrayByValue (строки 10–13), что она создает и инициализирует локальный объект класса myArray с помощью конструктора, получающего список инициализаторов с целочисленными значениями (строка 11). Этот конструктор отображает при каждом вызове строку:

MyArray(initializer_list) constructor

Затем функция getArrayByValue возвращает этот локальный объект по значению (строка 12). Вы могли ожидать, что возврат объекта по значению приведет к созданию временной копии для использования в вызывающем коде. Если бы это было так, то вызывался бы копирующий конструктор для копирования локального объекта. Вы также могли ожидать, что локальный объект выйдет из области видимости и его деструктор будет вызван, когда функция getArrayByValue вернет управление вызывающему коду. Однако ни копирующий конструктор, ни деструктор не вывели никаких сообщений в выходных данных.

Причина заключается в том, что компилятор повышает быстродействие методом, который называется оптимизацией именованного возвращаемого значения (named return value optimization, или сокращенно NRVO). Когда компилятор видит, что локальный объект создан, возвращен из функции по значению, а затем используется для инициализации объекта в точке вызова, компилятор создает объект непосредственно в точке вызова, где он будет использован. При этом не создается временный объект и не происходят дополнительные вызовы конструктора и деструктора, упомянутые выше. Первоначально это была необязательная оптимизация, но в C++17 она стала обязательной.

Создание и инициализация объекта ints5 значением rvalue, возвращенным функцией std::move

Копирующий конструктор вызывается при инициализации одного объекта класса myArray другим, который представлен значением lvalue. Копирующий конструктор копирует содержимое своего аргумента. Это похоже на операцию «копировать и вставить» в текстовом редакторе: после такой операции у вас есть две копии данных.

C++ также поддерживает семантику перемещения, которая помогает компилятору избежать дополнительной нагрузки, вызванной бесполезным копированием объектов. Перемещение работает аналогично операции «вырезать и вставить» в текстовом редакторе: данные перемещаются из одного места в другое. Перемещающий конструктор перемещает в новый объект все ресурсы исходного, уже ненужного объекта. Такой конструктор получает ссылку rvalue (C++11), которая, как вы увидите, объявлена как ИмяТипа&&. Ссылки rvalue могут ссылаться только на значения rvalue. Как правило, это временные объекты или объекты, подлежащие уничтожению, — xvalue (сокращение от «eXpiring value»).

В строке 88 перемещающий конструктор класса myArray используется для инициализации объекта ints5, а в строках 90–91 отображаются размер и содержимое нового объекта. Объект ints4 является значением lvalue, поэтому он не может быть передан непосредственно в перемещающий конструктор. Если вам больше не нужны ресурсы объекта, вы можете преобразовать его из lvalue в rvalue, передав этот объект функции std::move стандартной библиотеки (C++11, заголовок ). Эта функция приводит свой аргумент к ссылке rvalue, сообщая компилятору, что содержимое объекта ints4 больше не требуется. Итак, в строке 88 вызывается перемещающий конструктор класса myArray и содержимое объекта ints4 перемещается в объект ints5.
___________________________________________________________________________________
85   // Преобразуем ints4 в ссылку rvalue функцией std::move
86   // и используем результат для инициализации объекта ints5
87   std::cout << "\n\nInitialize ints5 with result of std::move(ints4)\n";
88   MyArray ints5{std::move(ints4)}; // Вызываем перемещающий конструктор
89
90   std::cout << fmt::format("\nints5 size: {}\ncontents: ", ints5.size())
91      << ints5
92      << fmt::format("\n\nSize of ints4 is now: {}", ints4.size());
93

Initialize ints5 with result of std::move(ints4)
MyArray move constructor

ints5 size: 3
contents: {10, 20, 30}

Size of ints4 is now: 0

Рекомендуется использовать функцию std::move так, как показано в этом коде, только если вы уверены, что переданный в std::move объект больше не будет использоваться. После перемещения исходный объект остается пустым (empty), поэтому с ним можно выполнить только две допустимые операции:
  • уничтожить его;
  • использовать его в левой части выражения присваивания, чтобы присвоить ему новое значение.
Как правило, вызывать функции пустого объекта недопустимо. Мы сделали это в строке 92 только для того, чтобы убедиться, что перемещающий конструктор действительно переместил ресурсы объекта: в выходных данных показан размер объекта ints4, равный 0.

Присваивание объекта ints5 объекту ints4 оператором перемещающего присваивания

В строке 96 оператор перемещающего присваивания класса myArray используется для перемещения содержимого объекта ints5 (10, 20 и 30) обратно в ints4. В строках 98–99 отображаются размер и содержимое обновленного объекта ints4. В строке 96 значение lvalue ints5 явно преобразуется в ссылку rvalue функцией std::move. Это означает, что объект ints5 больше не нуждается в своих ресурсах, поэтому компилятор может переместить их в ints4 и вызывает оператор перемещающего присваивания. Только в демонстрационных целях мы выводим в строке 100 размер объекта ints5, показывая, что оператор перемещающего присваивания действительно переместил ресурсы. Напомним, что вы никогда не должны вызывать функции пустого объекта!
___________________________________________________________________________________
94   // Перемещаем содержимое объекта ints5 в объект ints4
95   std::cout << "\n\nMove ints5 into ints4 via move assignment\n";
96   ints4 = std::move(ints5); // Перемещающее присваивание
97
98   std::cout << fmt::format("\nints4 size: {}\ncontents: ", ints4.size())
99      << ints4
100      << fmt::format("\n\nSize of ints5 is now: {}", ints5.size());
101

Move ints5 into ints4 via move assignment
MyArray move assignment operator

ints4 size: 3
contents: {10, 20, 30}

Size of ints5 is now: 0

Преобразование типа myArray в bool для проверки наличия элементов в ints5

Многие языки программирования позволяют использовать объект контейнерного класса в качестве условия, показывающего наличие элементов в контейнере. Для класса myArray мы определили конвертирующий оператор bool, который возвращает true, если объект myArray содержит элементы (то есть его размер больше 0), или false при отсутствии элементов. В тех случаях, когда нужны значения bool, например, в условиях управляющих инструкций, C++ может неявно вызывать этот конвертирующий оператор. Это называется контекстным преобразованием (contextual conversion). В строке 103 объект ints5 используется в качестве условия, которое вызывает конвертирующий оператор bool. Мы только что переместили ресурсы объекта ints5 в ints4, и оператор возвращает false, поскольку ints5 теперь пуст. Еще раз подчеркнем, что вы никогда не должны вызывать функции пустых объектов: мы делаем это только для проверки корректности перемещения ресурсов из объекта ints5.
___________________________________________________________________________________
102   // Проверяем контекстным преобразованием в bool наличие элементов в ints5
103   if (ints5) {
104      std::cout << "\n\nints5 contains elements\n";
105   }
106   else {
107      std::cout << "\n\nints5 is empty\n";
108   }
109

ints5 is empty

Преинкремент элементов ints4 перегруженным оператором префиксного инкремента (++)

Некоторые библиотеки поддерживают массовые (broadcast) операции, то есть могут применять одну и ту же операцию к каждому элементу структуры данных. Например, рассмотрим популярную библиотеку NumPy языка программирования Python. Структура ndarray (n-мерный массив) этой библиотеки перегружает многие арифметические операторы. Они выполняют математические операции над каждым элементом объекта типа ndarray. В NumPy следующий код на языке Python прибавляет 1 к каждому элементу объекта numbers типа ndarray — инструкция итерации не требуется:

numbers += 1 # В языке Python нет оператора ++

Мы добавили эту возможность в класс myArray. В строке 111 отображается содержимое объекта ints4, затем в строке 112 выполняется префиксный инкремент всего объекта (выражение ++ints4), то есть к каждому его элементу прибавляется 1. Результатом этого выражения является обновленный объект. Мы выводим его содержимое перегруженным оператором вставки в поток (<<).
___________________________________________________________________________________
110   // Прибавляем 1 к каждому элементу ints4 перегруженным оператором
         префиксного инкремента
111   std::cout << "\nints4: " << ints4;
112   std::cout << "\npreincrementing ints4: " << ++ints4;
113

ints4: {10, 20, 30}
preincrementing ints4: {11, 21, 31}

Постинкремент элементов ints4 перегруженным оператором постфиксного инкремента (++)

В строке 115 выражение ints4++ увеличивает все элементы объекта с помощью оператора постфиксного инкремента. Напомним, что эта операция возвращает предыдущее значение своего операнда, что подтверждается выводом программы. Выходные данные, показывающие моменты вызовов копирующего конструктора и деструктора, взяты из реализации оператора постфиксного инкремента, обсуждаемой в разделе 11.6.14.
___________________________________________________________________________________
114   // Прибавляем 1 к каждому элементу ints4 перегруженным оператором
         постфиксного инкремента
115   std::cout << "\n\npostincrementing ints4: " << ints4++ << "\n";
116   std::cout << "\nints4 now contains: " << ints4;
117

postincrementing ints4: MyArray copy constructor
{11, 21, 31}
MyArray destructor

ints4 now contains: {12, 22, 32}

Прибавление значения к каждому элементу объекта ints4 перегруженным оператором сложения с присваиванием (+=)

Для массовых операций класс myArray также предоставляет перегруженный составной оператор присваивания (+=), прибавляющий целое число к каждому элементу. В строке 119 мы суммируем каждый элемент объекта ints4 с числом 7 и отображаем обновленное содержимое. Обратите внимание, что неперегруженные версии операторов ++ и += по-прежнему работают с отдельными элементами myArray, которые являются целыми числами.
___________________________________________________________________________________
118   // Прибавляем число к каждому элементу ints4 перегруженным оператором
         постфиксного инкремента
119   std::cout << "\n\nAdd 7 to every ints4 element: " << (ints4 += 7)
120      << "\n";
121   }

Add 7 to every ints4 element: {19, 29, 39}

Уничтожение всех объектов класса myArray

Когда функция main завершается, вызываются деструкторы пяти объектов, созданных в функции main. Они выводят последние пять строк выходных данных программы.

MyArray destructor
MyArray destructor
MyArray destructor
MyArray destructor
MyArray destructor

Об авторах
Пол Дж. Дейтел (Paul J. Deitel), генеральный директор и технический директор компании Deitel & Associates, Inc., — выпускник Массачусетского технологического института, 42 года работающий в компьютерной сфере. Пол — один из самых опытных в мире преподавателей языков программирования, он с 1992 года ведет профессиональные курсы для разработчиков программного обеспечения. Прочитал сотни курсов по программированию для академических, промышленных, правительственных и военных организаций, которые являются клиентами Deitel & Associates. Среди них Калифорнийский университет, Cisco, IBM, Siemens, Sun Microsystems (теперь Oracle), Dell, Fidelity, NASA (Космический центр им. Кеннеди), Национальная лаборатория сильных штормов, ракетный полигон White Sands, Rogue Wave Software, Boeing, Puma, iRobot и многие другие. Пол Дж. Дейтел и его соавтор, доктор Харви М. Дейтел, являются авторами одних из самых популярных в мире учебников по языкам программирования, профессиональных книг, видеофильмов и интерактивных мультимедийных электронных курсов, а также ведущими тренингов.

Харви М. Дейтел (Harvey M. Deitel), председатель совета директоров и директор по стратегии компании Deitel & Associates, Inc., имеет более 60 лет опыта работы в области вычислительной техники. Он получил степени бакалавра и магистра электротехники в Массачусетском технологическом институте и степень доктора математики в Бостонском университете (изучал вычислительную технику по всем этим программам еще до того, как в этих вузах были созданы факультеты компьютерных наук). Харви М. Дейтел имеет большой опыт преподавания в отраслевых организациях и колледжах, он был председателем факультета компьютерных наук в Бостонском колледже, а затем вместе с сыном Полом основал в 1991 году компанию Deitel & Associates. Публикации Дейтелов получили международное признание: более 100 переводов опубликованы на японском, немецком, русском, испанском, французском, польском, итальянском, упрощенном китайском, традиционном китайском, корейском, португальском, греческом, урду и турецком языках. Доктор Дейтел прочитал сотни курсов по программированию для академических, корпоративных, правительственных и военных клиентов.

Более подробно с книгой можно ознакомиться на сайте издательства:

» Оглавление
» Отрывок

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Для Хаброжителей скидка 25% по купону — C++

Комментарии (14)


  1. Fedorkov
    14.05.2024 16:09

    C++20 для программистов

    А бывает для непрограммистов?


    1. sci_nov
      14.05.2024 16:09

      Для профессоров, например.


      1. alexandrustinov
        14.05.2024 16:09

        Или просто Modern C++ настолько оторвался от общей канвы (или от реальности), что теперь требуется отдельное руководство для обычных (к примеру С, C#, Java) программистов, настолько новые "чумовые фичи" диковинные и неочевидные

        В Java есть move semantics или perfect forwarding? А где оно вообще есть еще, кроме Modern C++?


        1. sci_nov
          14.05.2024 16:09

          Я думаю всё проще: нельзя ведь назвать просто "С++20". Надо что-нибудь добавить) Можно было бы написать "для профессионалов")


  1. voldemar_d
    14.05.2024 16:09

    Жаль, что книга на 1000 страниц и в мягкой обложке. При том, что стоит недёшево (да, я знаю, что такие книги дешёвыми и не бывают, но тем не менее).


    1. slonoten
      14.05.2024 16:09

      В советское время ценное издание можно было отдать в переплётную мастерскую. Много таких книг было в доме. Мягкого переплёта хватит, чтобы прочитать книгу один раз. Если захочется вернуться или найти что-то из книги, то удобней использовать электронную копию, которая лежит в облаке.


      1. voldemar_d
        14.05.2024 16:09
        +2

        Такую толстую книгу, имхо, переплести в толстый переплёт вряд ли получится. Хотя, я не спец в этом.

        Лично мне уже в первый раз удобнее читать в толстом переплёте. Впрочем, никому не навязываю. Просто жаль.


    1. alexandrustinov
      14.05.2024 16:09
      +4

      Жаль, что книга на 1000 страниц и в мягкой обложке. 

      Судя по оглавлению там на первых 250 страницах будут в очередной раз учить операторам if(), for(), while(), делать пузырьковую сортировку и числа Фиббоначи считать или в цикле, или рекурсией. Наверняка на двух страницах походу расскажут, сколько байт в char, int, float и double и прочие подобные актуальные модерновые С++20 вещи.

      А то вдруг ты забыл или не знал. Как раз освежишь знания-то.


  1. KirpaPuto
    14.05.2024 16:09
    +2

    Я подробно не читал ни оглавление, ни отрывок. Просто "пробежал" глазами и сразу наткнулся на пункт 5.20 в оглавлении:

    ...вы собираетесь ЭТО продавать?

    Затем пробежался по отрывку. Вы описываете сборку проекта и описываете работу программы, не приводя ни одной строки этой программы. Ага, текст программы можно найти "вон там" (в таком-то каталоге такого-то проекта). Чем и для кого это может быть полезным? Книга для оффлайн чтения (как минимум первой главы) бесполезна. Может быть, дольше будет интереснее, но вы продемонстрировали только первую главу.


    1. KirpaPuto
      14.05.2024 16:09
      +3

      И - масса ссылок на web - ресурсы. Почему ссылки не оформлены как QR коды?


    1. KirpaPuto
      14.05.2024 16:09

      Ой, это же перевод. Извинения.


    1. Crazy_13
      14.05.2024 16:09
      +1

      Это глава о шифровании )


      1. voldemar_d
        14.05.2024 16:09

        Надо в комплекте сразу брать еще одну книгу (не реклама, просто название подходящее, кмк :-)

        Книга: «Креативный программист» / Хабр (habr.com)


  1. valeramikhaylovsky
    14.05.2024 16:09
    +1

    Такие книги быстро теряют актуальность, выйдет С++26 и опять надо учить всё заново.