std::function i std::bind
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 86 z 104.
Chociaż lambdy są potężne, czasami trzeba przechowywać wywoływalne obiekty różnych typów w jednolity sposób albo dostosować istniejące funkcje do wymaganego podpisu. std::function i std::bind z nagłówka <functional> rozwiązują te problemy.
std::function to opakowanie z wymazanym typem, które może przechowywać dowolny obiekt wywoływalny zgodny z określoną sygnaturą — funkcje, lambdy lub obiekty funkcyjne:
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
std::function<int(int, int)> operation;
operation = add; // Zwykła funkcja
std::cout << operation(3, 4) << "\n"; // 7
operation = [](int a, int b) { return a * b; }; // Funkcja lambda
std::cout << operation(3, 4) << "\n"; // 12
}std::bind tworzy nowy obiekt wywoływalny, ustalając niektóre argumenty istniejącej funkcji. Użyj std::placeholders::_1, _2 itd., aby oznaczyć argumenty, które pozostają zmienne:
#include <iostream>
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
int main() {
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1);
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1);
swapped("Bob", "Hi"); // Hi, Bob!
}Te narzędzia są szczególnie przydatne w programowaniu obiektowym do przechowywania funkcji zwrotnych jako składowych klasy lub implementowania wzorca Strategia. Jednak we współczesnym C++ często preferuje się lambdy zamiast std::bind ze względu na lepszą czytelność i wydajność — używaj std::bind głównie wtedy, gdy musisz zmienić kolejność argumentów lub pracujesz ze starszym kodem.
Wyzwanie
ŁatwyZbudujmy konfigurowalny kalkulator, który demonstruje możliwości std::function i std::bind. Utworzysz system, w którym operacje matematyczne można przechowywać, zamieniać i dostosowywać w czasie działania programu, pokazując, jak te narzędzia umożliwiają elastyczne zarządzanie funkcjami zwrotnymi.
Podzielisz swój kod na trzy pliki:
MathOperations.h: Zdefiniuj zbiór samodzielnych funkcji matematycznych, które będą pełnić rolę biblioteki operacji.Utwórz następujące funkcje:
add(int a, int b): zwraca sumęsubtract(int a, int b): zwraca różnicę (a - b)multiply(int a, int b): zwraca iloczynpower(int base, int exponent, int multiplier): zwracamultiplier * (base ^ exponent). Użyj prostej pętli do obliczenia potęgi (załóż nieujemne wykładniki).
Calculator.h: Zdefiniuj klasęCalculator, która używastd::functiondo dynamicznego przechowywania i wykonywania operacji.Twój kalkulator powinien mieć:
- Prywatne pole
std::function<int(int, int)>do przechowywania bieżącej operacji dwuargumentowej setOperation(std::function<int(int, int)> op): ustawia bieżącą operacjęcalculate(int a, int b): wykonuje przechowywaną operację i zwraca wynik
Dołącz niezbędne nagłówki (
<functional>) i pamiętaj o dołączeniu nagłówka MathOperations.- Prywatne pole
main.cpp: Wczytaj trzy dane wejściowe:- Nazwę operacji:
add,subtract,multiplylubsquare - Pierwszą liczbę (liczbę całkowitą)
- Drugą liczbę (liczbę całkowitą)
Utwórz obiekt
Calculatori skonfiguruj go na podstawie nazwy operacji:- Dla
add,subtractimultiply: przypisz odpowiednią funkcję bezpośrednio do kalkulatora - Dla
square: użyjstd::bind, aby utworzyć wyspecjalizowaną wersję funkcjipower, która zawsze używa wykładnika 2 i mnożnika 1. Powiązana funkcja powinna przyjmować dwa argumenty, z których tylko pierwszy jest używany jako podstawa (drugi argument można zignorować za pomocą symbolu zastępczego).
Po ustawieniu operacji wywołaj
calculate()z podanymi dwiema liczbami i wyświetl:Result: [value]Następnie zademonstruj zmianę operacji, przypisując kalkulatorowi lambdę, która zwraca
a + b + 100, uruchamiając ją z tymi samymi danymi wejściowymi i wyświetlając:With bonus: [value]- Nazwę operacji:
Na przykład dla danych wejściowych add, 10 i 5:
Result: 15
With bonus: 115Dla danych wejściowych multiply, 7 i 3:
Result: 21
With bonus: 110Dla danych wejściowych square, 4 i 0:
Result: 16
With bonus: 104To wyzwanie pokazuje, jak std::function zapewnia jednolity sposób przechowywania różnych typów obiektów wywoływalnych (zwykłych funkcji, funkcji powiązanych i lambd), a std::bind pozwala dostosowywać istniejące funkcje przez ustalenie niektórych ich argumentów.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"
using namespace std;
int main() {
// Wczytaj dane wejściowe
string operation;
int num1, num2;
cin >> operation >> num1 >> num2;
// Utwórz instancję Calculator
Calculator calc;
// TODO: Skonfiguruj kalkulator na podstawie nazwy operacji
// Dla "add", "subtract", "multiply": przypisz bezpośrednio odpowiednią funkcję
// Dla "square": użyj std::bind, aby utworzyć wyspecjalizowaną wersję funkcji power
// która zawsze używa wykładnika 2 i mnożnika 1
// Wskazówka: użyj std::placeholders::_1 jako argumentu podstawy
if (operation == "add") {
// Twój kod tutaj
} else if (operation == "subtract") {
// Twój kod tutaj
} else if (operation == "multiply") {
// Twój kod tutaj
} else if (operation == "square") {
// Twój kod tutaj — użyj std::bind z funkcją power
}
// TODO: Wywołaj calculate() i wypisz wynik
// Format: "Result: [value]"
// TODO: Zademonstruj zamianę operacji
// Przypisz kalkulatorowi lambdę, która zwraca a + b + 100
// Ponownie wywołaj calculate() i wypisz wynik
// Format: "With bonus: [value]"
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generyczny12Nowoczesne funkcje C++
Semantyka przenoszenia i r-wartościDoskonałe przekazywanieWyrażenia lambda w szczegółachstd::function i std::bindconstexpr i constevalWiązania strukturalneoptional, variant, anyPoćwicz samodzielnie: Kompilator C++ online