Menu
Coddy logo textTech

ביטויי 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, משווים מותאמים אישית או מטפלי אירועים — בלי להגדיר אובייקטים נפרדים של פונקציות.

challenge icon

אתגר

קל

בואו נבנה מערכת לטיפול באירועים שמדגימה את העוצמה של ביטויי 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: קראו שני קלטים:
    1. מספר בסיס (מספר שלם)
    2. מכפיל (מספר שלם)

    צרו EventDispatcher והדגימו טכניקות שונות ללכידת lambda באמצעות הוספת שלוש פונקציות callback:

    1. lambda שלוכדת את מספר הבסיס by value ומדפיסה: Base value: [base]
    2. lambda שלוכדת את המכפיל by reference, מגדילה אותו ב-1, ואז מדפיסה: Multiplier after increment: [multiplier]
    3. 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;
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין