Поясним сказанное на примере. Напишем маленькую простую программу, которая считывает из консоли два числа, складывает их и результат выводит на экран (Листинг 19).
Листинг 19. Маленькая простая программа
#include <iostream>
int Calculate(int a, int b)
{
return a + b;
}
int main()
{
int a, b;
std::cin >> a >> b;
int result = Calculate(a, b);
std::cout << result;
}
Откомпилируем код с выключенной оптимизацией и запустим на выполнение. Посмотрим дизассемблерный участок кода [11 - Этот код получен с помощью компилятора Microsoft Visual studio версии 19.23.28106.4. Другие компиляторы могут генерировать отличающийся код, но принцип останется прежним.], в котором производится вызов функции (Листинг 20):
Листинг 20. Дизассемблерный код с выключенной оптимизацией:
int Calculate(int a, int b)
{
00007FF6DA741005 and al,8 // 1
return a + b;
00007FF6DA741008 mov eax,dword ptr [b] // 2
00007FF6DA74100C mov ecx,dword ptr [a] // 3
00007FF6DA741010 add ecx,eax // 4
00007FF6DA741012 mov eax,ecx // 5
}
00007FF6DA741014 ret // 6
int main()
{
…….
int result = Calculate(a, b);
00007FF6DA741053 mov edx,dword ptr [b] // 7
00007FF6DA741057 mov ecx,dword ptr [a] // 8
00007FF6DA74105B call Calculate (07FF6DA741000h) // 9
00007FF6DA741060 mov dword ptr [result],eax // 10
…….
В строках 7 и 8 введенные значения a и b сохраняются в регистрах. В строке 9 выполняется вызов функции. В строке 1 выполняется обнуление результата, в строках 2 и 3 переданные значения копируются в регистры, в строке 4 выполняется сложение, в строке 5 результат копируется обратно в регистр, в строке 6 выполняется выход из функции, в строке 10 результат вычисления функции копируется в переменную результата.
Теперь включим оптимизацию, откомпилируем и посмотрим на код (Листинг 21):
Листинг 21. Дизассемблерный код с включенной оптимизацией
int main()
{
…….
int result = Calculate(a, b);
00007FF7D5B11033 mov edx,dword ptr [b]
00007FF7D5B11037 add edx,dword ptr [a]
Как видим, для вычислений у нас всего две операции: запись в регистр значения b и добавление к нему значения a. Код встроен в поток выполнения, вызов функции не производится. Ощутимая разница, не правда ли?
2.5. Лямбда-выражение
2.5.1. Концепция
Лямбда-выражение[12 - В литературе можно встретить термин «лямбда-функция», но в стандарте С++ он именуется как “lambda-expression”, что в переводе означает «лямбда-выражение».] – это локальная неименованная функция, которая, подобно обычной функции, может принимать входные параметры и возвращать результат. Особенностью лямбда-выражений, отличающих их от обычных функций, является возможность захвата переменных.
Графическое изображение обратного вызова с помощью лямбда-выражения представлено на Рис. 15. Исполнитель реализуется в виде какой-либо исполняемой функции, в качестве которой могут выступать глобальная функция, статический метод класса, метод-член класса, перегруженный оператор. Код обратного вызова упаковывается в лямбда-выражение, в качестве контекста выступают захваченные переменные. При настройке лямбда-выражение как аргумент сохраняется в инициаторе. Инициатор осуществляет обратный вызов посредством вызова хранимого выражения, передавая ему требуемую информацию. Контекст здесь передавать не нужно, поскольку внутри тела лямбда-выражения доступны все захваченные переменные.
Рис. 15. Реализация обратного вызова с помощью лямбда-выражения.