std::function ו־std::bind
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 86 מתוך 104.
למרות שלמבדה הן כלי רב־עוצמה, לפעמים צריך לאחסן אובייקטים שניתנים לקריאה מסוגים שונים באופן אחיד, או להתאים פונקציות קיימות כך שיתאימו לחתימה נדרשת. std::function ו־std::bind מהכותרת <functional> פותרים את הבעיות האלה.
std::function היא מעטפת שמוחקת את הטיפוס ויכולה להכיל כל ישות שניתן לקרוא לה ותואמת לחתימה מסוימת — פונקציות, למבדות או אובייקטי פונקציה:
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
std::function<int(int, int)> operation;
operation = add; // פונקציה רגילה
std::cout << operation(3, 4) << "\n"; // 7
operation = [](int a, int b) { return a * b; }; // למבדה
std::cout << operation(3, 4) << "\n"; // 12
}std::bind יוצרת אובייקט שניתן לקרוא לו על ידי קיבוע חלק מהארגומנטים של פונקציה קיימת. השתמשו ב-std::placeholders::_1, ב-_2 וכן הלאה כדי לסמן ארגומנטים שנותרים משתנים:
#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"); // שלום, Alice!
auto swapped = std::bind(greet, _2, _1);
swapped("Bob", "Hi"); // היי, Bob!
}הכלים האלה שימושיים במיוחד בתכנות מונחה עצמים לאחסון פונקציות callback כחברי מחלקה או למימוש תבנית האסטרטגיה. עם זאת, ב־C++ מודרנית לרוב מעדיפים ביטויי lambda על פני std::bind לשיפור הקריאות והביצועים — השתמשו ב־std::bind בעיקר כשצריך לשנות את סדר הארגומנטים או לעבוד עם קוד מדור קודם.
אתגר
קלבואו נבנה מחשבון שניתן להגדיר, המדגים את העוצמה של std::function ושל std::bind. תיצור מערכת שבה ניתן לשמור, להחליף ולהתאים אישית פעולות מתמטיות בזמן הריצה: כך נראה כיצד הכלים האלה מאפשרים ניהול גמיש של קריאות חוזרות.
תארגן את הקוד שלך בשלושה קבצים:
MathOperations.h: הגדר אוסף של פונקציות מתמטיות עצמאיות שישמשו כספריית הפעולות שלך.צור את הפונקציות האלה:
add(int a, int b): מחזירה את הסכוםsubtract(int a, int b): מחזירה את ההפרש (a - b)multiply(int a, int b): מחזירה את המכפלהpower(int base, int exponent, int multiplier): מחזירהmultiplier * (base ^ exponent). השתמש בלולאה פשוטה כדי לחשב את החזקה (נניח שהמעריכים אינם שליליים).
Calculator.h: הגדר מחלקה בשםCalculatorשמשתמשת ב-std::functionכדי לשמור פעולות ולהפעיל אותן באופן דינמי.ה-Calculator שלך צריך לכלול:
- חבר פרטי
std::function<int(int, int)>לשמירת הפעולה הבינארית הנוכחית setOperation(std::function<int(int, int)> op): מגדירה את הפעולה הנוכחיתcalculate(int a, int b): מפעילה את הפעולה השמורה ומחזירה את התוצאה
כלול את הכותרות הדרושות (
<functional>) וודא שאתה כולל את כותרת MathOperations שלך.- חבר פרטי
main.cpp: קרא שלוש קלטים:- שם הפעולה:
add,subtract,multiplyאוsquare - המספר הראשון (מספר שלם)
- המספר השני (מספר שלם)
צור
Calculatorוהגדר אותו בהתאם לשם הפעולה:- עבור
add,subtractו-multiply: הקצה את הפונקציה המתאימה ישירות למחשבון - עבור
square: השתמש ב-std::bindכדי ליצור גרסה מותאמת שלpowerשתשתמש תמיד במעריך 2 ובמכפיל 1. הפונקציה הקשורה צריכה לקבל שני ארגומנטים, כאשר רק הראשון משמש כבסיס (אפשר להתעלם מהארגומנט השני באמצעות מציין מיקום).
לאחר הגדרת הפעולה, קרא ל-
calculate()עם שני המספרים שלך והדפס:Result: [value]לאחר מכן הדגם החלפה של פעולות באמצעות הקצאת lambda שמחזירה
a + b + 100למחשבון, הפעל אותה עם אותן קלטים והדפס:With bonus: [value]- שם הפעולה:
לדוגמה, עם הקלטים add, 10 ו-5:
Result: 15
With bonus: 115עם הקלטים multiply, 7 ו-3:
Result: 21
With bonus: 110עם הקלטים square, 4 ו-0:
Result: 16
With bonus: 104האתגר הזה מדגים כיצד std::function מספק דרך אחידה לשמירת סוגים שונים של אובייקטים קריאים (פונקציות רגילות, פונקציות קשורות ו-lambda), בעוד std::bind מאפשר להתאים פונקציות קיימות באמצעות קיבוע של חלק מהארגומנטים שלהן.
נסו בעצמכם
#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"
using namespace std;
int main() {
// קראו את הקלט
string operation;
int num1, num2;
cin >> operation >> num1 >> num2;
// צרו מופע של Calculator
Calculator calc;
// TODO: הגדירו את המחשבון לפי שם הפעולה
// עבור "add", "subtract", "multiply": הקצו ישירות את הפונקציה המתאימה
// עבור "square": השתמשו ב-std::bind כדי ליצור גרסה מותאמת של power
// שמשתמשת תמיד במעריך 2 ובמכפיל 1
// רמז: השתמשו ב-std::placeholders::_1 עבור ארגומנט הבסיס
if (operation == "add") {
// הקוד שלכם כאן
} else if (operation == "subtract") {
// הקוד שלכם כאן
} else if (operation == "multiply") {
// הקוד שלכם כאן
} else if (operation == "square") {
// הקוד שלכם כאן - השתמשו ב-std::bind עם הפונקציה power
}
// TODO: קראו ל-calculate() והדפיסו את התוצאה
// פורמט: "Result: [value]"
// TODO: הדגימו החלפה בין פעולות
// הקצה למחשבון פונקציית lambda שמחזירה a + b + 100
// קרא שוב ל־calculate() והדפס את התוצאה
// פורמט: "With bonus: [value]"
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנרי12תכונות מודרניות ב־C++
סמנטיקת העברה ו־rvaluesהעברה מושלמתביטויי Lambda לעומקstd::function ו־std::bindconstexpr ו־constevalקישור מובנהoptional, variant, anyתרגלו בעצמכם: קומפיילר C++ אונליין