ביטויי Lambda לעומק
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 85 מתוך 104.
ביטויי למבדה, שהוצגו ב־C++11, הם פונקציות אנונימיות שאפשר להגדיר ישירות במקום. אף שכבר ראית למבדות בסיסיות עם אלגוריתמים של STL, הבנת התחביר המלא שלהן פותחת אפשרויות עוצמתיות ללכידת משתנים ולקביעת אופן הגישה אליהם.
תחביר הלמבדה המלא הוא: [capture](parameters) mutable -> return_type { body }. תנאי הלכידה קובע לאילו משתנים חיצוניים הלמבדה יכולה לגשת וכיצד:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // עותק של x
auto byRef = [&y]() { y += 5; }; // הפניה ל-y
auto allByValue = [=]() { return x + y; }; // העתקה של הכול
auto allByRef = [&]() { x++; y++; }; // הפניה לכול
auto mixed = [x, &y]() { y += x; }; // שילוב של שניהם
byRef();
std::cout << y << "\n"; // 25
}כברירת מחדל, משתנים שנלכדו לפי ערך הם const בתוך הלמבדא. מילת המפתח mutable מאפשרת לשנות את העותקים האלה:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // משנה את העותק של ה-lambda
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - המקור לא השתנהב-C++14 נוספו לכידות אתחול, שמאפשרות ליצור משתנים חדשים או להעביר אובייקטים אל תוך הלמבדה:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // הבעלות הועברה אל lambdaלמבדות שימושיות במיוחד ב-OOP כשצריך להעביר התנהגות כפרמטר — עבור פונקציות callback, משווים מותאמים אישית או מטפלי אירועים — בלי להגדיר אובייקטים נפרדים של פונקציות.
אתגר
קלבואו נבנה מערכת לטיפול באירועים שמדגימה את העוצמה של ביטויי lambda עם אופני לכידה שונים. תיצרו מפיץ אירועים פשוט ששומר פונקציות callback ומפעיל אותן, כדי להדגים כיצד lambdas יכולות ללכוד מצב חיצוני בדרכים שונות.
תארגנו את הקוד בשלושה קבצים:
EventDispatcher.h: הגדירו מחלקה בשםEventDispatcherשמנהלת פונקציות callback של אירועים.המפיץ שלכם צריך לשמור פונקציות callback באמצעות
std::vectorשלstd::function<void()>. כללו את המתודות הבאות:addCallback(std::function<void()> callback): מוסיפה פונקציית callback לרשימהfireAll(): מפעילה את כל פונקציות ה-callback שנשמרו, לפי הסדרclear(): מסירה את כל פונקציות ה-callback
תצטרכו לכלול את
<functional>ואת<vector>.EventDispatcher.cpp: ממשו את המתודות של המפיץ. המתודהfireAll()צריכה פשוט לעבור על כל פונקציות ה-callback ולהפעיל כל אחת מהן.main.cpp: קראו שני קלטים:- מספר בסיס (מספר שלם)
- מכפיל (מספר שלם)
צרו
EventDispatcherוהדגימו טכניקות שונות ללכידת lambda באמצעות הוספת שלוש פונקציות callback:- lambda שלוכדת את מספר הבסיס by value ומדפיסה:
Base value: [base] - lambda שלוכדת את המכפיל by reference, מגדילה אותו ב-1, ואז מדפיסה:
Multiplier after increment: [multiplier] - lambda מסוג mutable שלוכדת משתנה מונה (שאותחל ל-0) לפי ערך, מגדילה אותו בכל פעם שהיא נקראת, ומדפיסה:
Call count: [counter]
אחרי הוספת כל פונקציות ה-callback, קראו ל-
fireAll()פעמיים כדי לראות כיצד אופני הלכידה השונים מתנהגים לאורך כמה הפעלות. בין שתי הקריאות ל-fireAll(), הדפיסו---כמפריד.לבסוף, אחרי שני הסבבים, הדפיסו את הערך הסופי של משתנה המכפיל מתוך main כדי להראות כיצד הלכידה לפי הפניה השפיעה עליו:
Final multiplier: [multiplier]
לדוגמה, עם הקלטים 10 ו-5:
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7עם הקלטים 42 ו-0:
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2שימו לב להתנהגויות המרכזיות: הלכידה לפי ערך משאירה את הבסיס המקורי ללא שינוי, הלכידה לפי הפניה משנה את משתנה המכפיל בפועל ב-main (והשינוי מצטבר בין הקריאות), והמונה של ה-lambda מסוג mutable מציג 1 בכל סבב. הסיבה לכך היא ש-fireAll() עוברת על פונקציות ה-callback לפי ערך: בכל סבב מופעל עותק חדש של std::function השמורה, והמונה שנלכד בה מתחיל ב-0 ומוגדל ל-1, כך שהמצב של פונקציית ה-callback השמורה עצמה לעולם אינו מתקדם.
נסו בעצמכם
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO: הוסיפו פונקציית lambda שלוכדת את base לפי ערך
// עליה להדפיס: "Base value: [base]"
// TODO: הוסיפו פונקציית lambda שלוכדת את multiplier לפי הפניה
// עליה להגדיל את multiplier ב-1, ואז להדפיס: "Multiplier after increment: [multiplier]"
// TODO: הוסיפו פונקציית lambda מסוג MUTABLE שלוכדת מונה (שמאותחל ל-0) לפי ערך
// עליה להגדיל את המונה ולהדפיס: "Call count: [counter]"
// TODO: קראו ל-fireAll() כדי להפעיל את כל פונקציות ה-callback
// TODO: הדפיסו "---" כמפריד
// TODO: קראו שוב ל-fireAll()
// TODO: הדפיסו את הערך הסופי של multiplier: "Final multiplier: [multiplier]"
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++ אונליין