C++ Academy · Урок

Выбор вызываемых объектов

Лямбды, bind или указатели

Урок 4 из 413 шагов

«Выбор вызываемых объектов» — бесплатный урок C++ Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C++ Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C++ Academy содержит 4 уроков всего.

Набор инструментов для вызываемых объектов

C++ предлагает несколько способов передавать поведение: указатели на функции, лямбды, функторы, std::bind и std::function. Каждый подходит для разных задач.

  • Обычный компромисс — выбор между скоростью и гибкостью.
  • Важна также понятность кода.
#include <iostream>

int twice(int x) { return x * 2; }

int main() {
    int (*fp)(int) = twice;
    auto lam = [](int x) { return x * 2; };
    std::cout << fp(5) << ' ' << lam(5) << '\n';
    return 0;
}

Когда использовать указатель на функцию

Выбирайте указатель на функцию, когда Вам нужна небольшая функция обратного вызова без состояния, совместимость с C или отсутствие накладных расходов.

#include <iostream>

void logMsg(const char* m) { std::cout << "LOG: " << m << '\n'; }

int main() {
    void (*handler)(const char*) = logMsg;
    handler("started");
    return 0;
}

Когда использовать лямбду

Лямбда — стандартный современный выбор: она лаконична, может захватывать состояние, а компилятор хорошо встраивает её в шаблоны и алгоритмы.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5};
    std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
    for (int x : v) std::cout << x << ' ';
    std::cout << '\n';
    return 0;
}

Захват состояния в лямбде

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

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    int threshold = 3;
    std::vector<int> v = {1, 2, 3, 4, 5};
    int c = std::count_if(v.begin(), v.end(), [threshold](int x) { return x > threshold; });
    std::cout << c << '\n';
    return 0;
}

Когда использовать функтор

Функтор (структура с operator()) подходит для повторно используемой именованной операции, которая может хранить настройки и использоваться в разных местах.

#include <iostream>
#include <vector>
#include <algorithm>

struct AboveThreshold {
    int limit;
    bool operator()(int x) const { return x > limit; }
};

int main() {
    std::vector<int> v = {1, 5, 2, 8};
    std::cout << std::count_if(v.begin(), v.end(), AboveThreshold{4}) << '\n';
    return 0;
}

Лямбда или bind

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

#include <iostream>

int power(int base, int exp) {
    int r = 1;
    for (int i = 0; i < exp; ++i) r *= base;
    return r;
}

int main() {
    auto square = [](int x) { return power(x, 2); };
    std::cout << square(6) << '\n';
    return 0;
}

Когда использовать std::function

Используйте std::function, когда необходимо хранить вызываемый объект неизвестного конкретного типа, например в члене класса или контейнере.

#include <iostream>
#include <functional>
#include <vector>

int main() {
    std::vector<std::function<int(int)>> pipeline;
    pipeline.push_back([](int x) { return x + 1; });
    pipeline.push_back([](int x) { return x * 3; });
    int v = 2;
    for (auto& f : pipeline) v = f(v);
    std::cout << v << '\n';
    return 0;
}

Шаблоны исключают накладные расходы

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

#include <iostream>

template <typename F>
int applyTwice(int x, F f) { return f(f(x)); }

int main() {
    std::cout << applyTwice(3, [](int n) { return n + 10; }) << '\n';
    return 0;
}

Рейтинг производительности

Примерный порядок такой: вызываемый объект, заданный шаблоном, или лямбда без захвата работают быстрее всего, указатель на функцию близок к ним, а std::function работает медленнее всего из-за косвенного вызова и возможного выделения памяти.

#include <iostream>
#include <functional>

int main() {
    auto fast = [](int x) { return x + 1; };
    std::function<int(int)> flexible = fast;
    std::cout << fast(1) << ' ' << flexible(1) << '\n';
    return 0;
}

Руководство по выбору

Краткое руководство:

  • Нужно хранить любой вызываемый объект? std::function.
  • Нужно только получать вызываемый объект в обобщенной функции? Параметр шаблона.
  • Поведение с состоянием, заданное непосредственно в коде? Лямбда.
  • Интерфейс C или отсутствие состояния? Указатель на функцию.
#include <iostream>
#include <functional>

void runStored(const std::function<void()>& cb) { cb(); }

template <typename F>
void runGeneric(F f) { f(); }

int main() {
    runStored([] { std::cout << "stored\n"; });
    runGeneric([] { std::cout << "generic\n"; });
    return 0;
}

Объединяем всё

Небольшая система событий может обобщенно получать обработчики, но хранить их в std::function, чтобы вызывать позже.

#include <iostream>
#include <functional>
#include <vector>

struct Dispatcher {
    std::vector<std::function<void(int)>> handlers;
    template <typename F> void on(F f) { handlers.emplace_back(f); }
    void fire(int e) { for (auto& h : handlers) h(e); }
};

int main() {
    Dispatcher d;
    d.on([](int e) { std::cout << "got " << e << '\n'; });
    d.fire(7);
    return 0;
}

Быстрая проверка

Проверьте своё понимание выбора вызываемых объектов.

Повторение

Вы научились выбирать вызываемый объект:

  • указатель на функцию — для обратных вызовов без состояния, совместимых с C
  • лямбда — стандартный выбор для поведения, заданного непосредственно в коде и содержащего состояние
  • функтор — для повторно используемых именованных операций
  • параметр шаблона — когда нужно только получать вызываемый объект (самый быстрый вариант)
  • std::function — когда необходимо хранить вызываемый объект неизвестного типа

На этом курс «Функциональные объекты и std::bind» завершен.

Можно начать бесплатно

Изучай C++ с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
51
Уроки
203

Часто задаваемые вопросы

Урок «Выбор вызываемых объектов» бесплатный?

Да — полный текст урока «Выбор вызываемых объектов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C++ Academy, подпишись на CoddyKit PRO. Курс C++ Academy содержит 4 уроков всего.

Чему я научусь в уроке «Выбор вызываемых объектов»?

Лямбды, bind или указатели Ты практикуешь C++ Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C++ Academy?

Предыдущий опыт не требуется. C++ Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Выбор вызываемых объектов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C++ Academy?

Да. Каждый урок C++ Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Указатели на функции
  2. std::function
  3. std::bind
  4. Выбор вызываемых объектов
← Назад к C++ Academy