std::function & std::bind
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 86 von 104.
Während Lambdas leistungsstark sind, müssen Sie manchmal aufrufbare Objekte mit unterschiedlichen Typen auf einheitliche Weise speichern oder bestehende Funktionen anpassen, um einer erforderlichen Signatur zu entsprechen. std::function und std::bind aus dem Header <functional> lösen diese Probleme.
std::function ist ein typlöschender Wrapper, der jedes aufrufbare Objekt (Callable) aufnehmen kann, das einer bestimmten Signatur entspricht – Funktionen, Lambdas oder Funktionsobjekte:
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
std::function<int(int, int)> operation;
operation = add; // Reguläre Funktion
std::cout << operation(3, 4) << "\n"; // 7
operation = [](int a, int b) { return a * b; }; // Lambda
std::cout << operation(3, 4) << "\n"; // 12
}std::bind erzeugt ein neues aufrufbares Objekt (Callable), indem einige Argumente einer bestehenden Funktion fixiert werden. Verwenden Sie std::placeholders::_1, _2 usw., um Argumente zu markieren, die variabel bleiben:
#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!
}Diese Tools sind in der OOP besonders nützlich, um Callbacks als Klassenmitglieder zu speichern oder das Strategy-Pattern zu implementieren. Modernes C++ bevorzugt jedoch oft Lambdas gegenüber std::bind für eine bessere Lesbarkeit und Performance – verwenden Sie std::bind hauptsächlich dann, wenn Sie Argumente umordnen müssen oder mit Legacy-Code arbeiten.
Aufgabe
EinfachLassen Sie uns einen konfigurierbaren Taschenrechner bauen, der die Leistungsfähigkeit von std::function und std::bind demonstriert. Sie werden ein System erstellen, in dem mathematische Operationen zur Laufzeit gespeichert, ausgetauscht und angepasst werden können – was zeigt, wie diese Werkzeuge ein flexibles Callback-Management ermöglichen.
Sie werden Ihren Code über drei Dateien organisieren:
MathOperations.h: Definieren Sie eine Sammlung von eigenständigen mathematischen Funktionen, die als Ihre Operationsbibliothek dienen werden.Erstellen Sie diese Funktionen:
add(int a, int b)— gibt die Summe zurücksubtract(int a, int b)— gibt die Differenz (a - b) zurückmultiply(int a, int b)— gibt das Produkt zurückpower(int base, int exponent, int multiplier)— gibtmultiplier * (base ^ exponent)zurück. Verwenden Sie eine einfache Schleife, um die Potenz zu berechnen (nehmen Sie nicht-negative Exponenten an).
Calculator.h: Definieren Sie eineCalculator-Klasse, diestd::functionverwendet, um Operationen dynamisch zu speichern und auszuführen.Ihr Calculator sollte Folgendes haben:
- Ein privates Mitglied
std::function<int(int, int)>, um die aktuelle binäre Operation zu speichern setOperation(std::function<int(int, int)> op)— setzt die aktuelle Operationcalculate(int a, int b)— führt die gespeicherte Operation aus und gibt das Ergebnis zurück
Fügen Sie die erforderlichen Header (
<functional>) ein und stellen Sie sicher, dass Sie Ihren MathOperations-Header einbinden.- Ein privates Mitglied
main.cpp: Lesen Sie drei Eingaben ein:- Operationsname:
add,subtract,multiplyodersquare - Erste Zahl (Ganzzahl)
- Zweite Zahl (Ganzzahl)
Erstellen Sie einen
Calculatorund konfigurieren Sie ihn basierend auf dem Operationsnamen:- Für
add,subtractundmultiply: Weisen Sie die entsprechende Funktion direkt dem Taschenrechner zu - Für
square: Verwenden Siestd::bind, um eine spezialisierte Version vonpowerzu erstellen, die immer den Exponenten 2 und den Multiplikator 1 verwendet. Die gebundene Funktion sollte zwei Argumente akzeptieren, wobei nur das erste als Basis verwendet wird (das zweite Argument kann mit einem Platzhalter ignoriert werden).
Nachdem Sie die Operation festgelegt haben, rufen Sie
calculate()mit Ihren zwei Zahlen auf und geben Sie Folgendes aus:Result: [value]Demonstrieren Sie dann das Austauschen von Operationen, indem Sie dem Taschenrechner ein Lambda zuweisen, das
a + b + 100zurückgibt, es mit denselben Eingaben ausführen und Folgendes ausgeben:With bonus: [value]- Operationsname:
Zum Beispiel mit den Eingaben add, 10 und 5:
Result: 15
With bonus: 115Mit den Eingaben multiply, 7 und 3:
Result: 21
With bonus: 110Mit den Eingaben square, 4 und 0:
Result: 16
With bonus: 104Diese Herausforderung zeigt, wie std::function eine einheitliche Möglichkeit bietet, verschiedene aufrufbare Typen (reguläre Funktionen, gebundene Funktionen und Lambdas) zu speichern, während std::bind es Ihnen ermöglicht, bestehende Funktionen anzupassen, indem Sie einige ihrer Argumente festlegen.
Spickzettel
Der Header <functional> bietet std::function und std::bind für die flexible Arbeit mit aufrufbaren Objekten (Callable Objects).
std::function ist ein typsicherer Wrapper (Type-Erased Wrapper), der jedes aufrufbare Objekt speichern kann, das einer bestimmten Signatur entspricht:
#include <functional>
std::function<int(int, int)> operation;
operation = add; // Reguläre Funktion
operation = [](int a, int b) { return a * b; }; // Lambdastd::bind erstellt ein neues aufrufbares Objekt, indem einige Argumente einer bestehenden Funktion fixiert werden. Verwenden Sie std::placeholders::_1, _2 usw. für variable Argumente:
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1); // Erstes Argument fixieren
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1); // Argumente umordnen
swapped("Bob", "Hi"); // Hi, Bob!Diese Werkzeuge sind nützlich, um Callbacks als Klassenmitglieder zu speichern oder flexible Callback-Systeme zu implementieren. Modernes C++ bevorzugt oft Lambdas gegenüber std::bind für eine bessere Lesbarkeit.
Probier es selbst
#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"
using namespace std;
int main() {
// Eingaben lesen
string operation;
int num1, num2;
cin >> operation >> num1 >> num2;
// Eine Calculator-Instanz erstellen
Calculator calc;
// TODO: Den Taschenrechner basierend auf dem Operationsnamen konfigurieren
// Für "add", "subtract", "multiply": Die entsprechende Funktion direkt zuweisen
// Für "square": std::bind verwenden, um eine spezialisierte Version von power zu erstellen
// die immer den Exponenten 2 und den Multiplikator 1 verwendet
// Hinweis: std::placeholders::_1 für das Basis-Argument verwenden
if (operation == "add") {
// Dein Code hier
} else if (operation == "subtract") {
// Dein Code hier
} else if (operation == "multiply") {
// Dein Code hier
} else if (operation == "square") {
// Dein Code hier - std::bind mit der power-Funktion verwenden
}
// TODO: calculate() aufrufen und das Ergebnis ausgeben
// Format: "Result: [value]"
// TODO: Das Austauschen von Operationen demonstrieren
// Einen Lambda-Ausdruck, der a + b + 100 zurückgibt, dem Taschenrechner zuweisen
// calculate() erneut aufrufen und das Ergebnis ausgeben
// Format: "With bonus: [value]"
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container12Moderne C++ Features
Move-Semantik & RvaluesPerfect ForwardingLambda-Ausdrücke im Detailstd::function & std::bindconstexpr und constevalStructured Bindingsoptional, variant, any