Menu
Coddy logo textTech

optional, variant, any

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 89 מתוך 104.

C++ מודרנית מספקת שלושה טיפוסי אוצר מילים בכותרות <optional>, <variant> ו-<any>, שעוזרים לך לטפל בערכים שעשויים להיות חסרים, להכיל אחד מכמה טיפוסים או לאחסן כל טיפוס שהוא.

std::optional<T> מייצג ערך שעשוי להתקיים או לא להתקיים — מושלם לפונקציות שעלולות להיכשל בלי לזרוק חריגה:

#include <iostream>
#include <optional>
std::optional<int> findIndex(const std::string& str, char c) {
    for (size_t i = 0; i < str.size(); ++i) {
        if (str[i] == c) return i;
    }
    return std::nullopt;  // אין ערך
}
int main() {
    auto result = findIndex("hello", 'l');
    if (result.has_value()) {
        std::cout << "Found at: " << *result << "\n";  // 2
    }
}

std::variant<Types...> הוא איחוד בטוח מבחינת טיפוסים שמכיל בדיוק אחד מהטיפוסים שצוינו בכל רגע נתון:

#include <iostream>
#include <variant>
#include <string>
int main() {
    std::variant<int, double, std::string> data;
    data = 42;
    std::cout << std::get<int>(data) << "\n";
    data = "hello";
    if (std::holds_alternative<std::string>(data)) {
        std::cout << std::get<std::string>(data) << "\n";
    }
}

std::any יכול להכיל ערך מכל סוג, שנקבע בזמן ריצה. השתמשו ב-std::any_cast כדי לקבל את הערך:

#include <iostream>
#include <any>
int main() {
    std::any value = 10;
    std::cout << std::any_cast<int>(value) << "\n";
    value = std::string("text");
    std::cout << std::any_cast<std::string>(value) << "\n";
}

בחרו ב-std::optional לערכים שיכולים להיות ריקים, ב-std::variant כשאתם יודעים מהם הטיפוסים האפשריים בזמן קומפילציה, וב-std::any רק כשאתם באמת זקוקים לגמישות בסוג בזמן ריצה.

challenge icon

אתגר

קל

בואו נבנה מערכת תצורה שמדגימה את העוצמה של טיפוסי אוצר מילים מודרניים ב־C++. תיצרו מנהל הגדרות גמיש שמטפל בערכים שעשויים להיות חסרים, להכיל טיפוסים שונים או לדרוש גמישות של טיפוסים בזמן ריצה.

תארגנו את הקוד בשלושה קבצים:

  • ConfigTypes.h: הגדירו את טיפוסי ערכי התצורה שלכם ופונקציות עזר.

    צרו פונקציה בשם parseValue שמקבלת מחרוזת ומנסה לפענח אותה כמספר שלם. אם המחרוזת מייצגת מספר שלם תקין, החזירו את המספר השלם כשהוא עטוף ב־std::optional. אם הפענוח נכשל (המחרוזת מכילה תווים שאינם מספריים), החזירו std::nullopt. לשם הפשטות, התייחסו למחרוזת כתקינה אם היא מכילה רק ספרות (ואולי סימן מינוס מוביל).

    צרו גם כינוי טיפוס בשם Setting באמצעות std::variant, שיכול להכיל int, double או std::string.

    לבסוף, צרו פונקציה בשם describeSetting שמקבלת Setting ומחזירה מחרוזת שמתארת איזה טיפוס הוא מכיל ומה ערכו, בפורמט הבא:

    • עבור int: Integer: [value]
    • עבור double: Double: [value]
    • עבור string: String: [value]
  • DynamicStore.h: צרו מאגר פשוט של מפתח-ערך באמצעות std::any.

    הגדירו מחלקה DynamicStore שיכולה לאחסן ערכים מכל טיפוס. עליה לכלול:

    • מתודה set שמקבלת מפתח מסוג מחרוזת וערך מסוג std::any, ומאחסנת אותם פנימית (השתמשו ב־std::map)
    • מתודה get שמקבלת מפתח ומחזירה את ערך ה־std::any (החזירו std::any ריק אם המפתח לא קיים)
    • מתודה hasKey שמחזירה true אם המפתח קיים
  • main.cpp: קראו שלושה קלטים:
    1. מחרוזת שעשויה להיות מספר (לבדיקת std::optional)
    2. ציון טיפוס: int, double או string
    3. ערך שמתאים לטיפוס הזה

    הדגימו את כל שלושת טיפוסי אוצר המילים:

    ראשית, השתמשו בפונקציה parseValue עם הקלט הראשון. אם הוא מכיל ערך, הדפיסו Parsed: [value]. אם הוא ריק, הדפיסו Parse failed.

    שנית, צרו וריאנט מסוג Setting על סמך ציון הטיפוס. אם הטיפוס הוא int, שמרו את הערך כמספר שלם. אם הוא double, שמרו אותו כמספר ממשי. אם הוא string, שמרו אותו כמחרוזת. לאחר מכן קראו ל־describeSetting והדפיסו את התוצאה.

    שלישית, צרו DynamicStore, שמרו את המחרוזת "config_loaded" תחת המפתח "status", ואז אחזרו אותה והדפיסו: Status: [value]. השתמשו ב־std::any_cast<std::string> כדי לחלץ את הערך.

לדוגמה, עם הקלטים 42, int ו־100:

Parsed: 42
Integer: 100
Status: config_loaded

עם הקלטים hello, double ו־3.14:

Parse failed
Double: 3.14
Status: config_loaded

עם הקלטים -15, string ו־username:

Parsed: -15
String: username
Status: config_loaded

זכרו לכלול את הכותרות המתאימות: <optional>, <variant>, <any>, <string> ו־<map> במקומות הנדרשים. השתמשו ב־std::holds_alternative וב־std::get כדי לעבוד עם הווריאנט שלכם, וב־has_value() או בהמרה בוליאנית ישירה כדי לבדוק את ה־optional שלכם.

נסו בעצמכם

#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"

int main() {
    // קרא שלושה ערכי קלט
    std::string input1;  // מחרוזת שעשויה להיות מספר (לבדיקת std::optional)
    std::string typeIndicator;  // מציין סוג: int, double או string
    std::string value;  // ערך התואם לסוג הזה
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: חלק 1 - בדוק את std::optional באמצעות parseValue
    // השתמש בפונקציה parseValue עם input1
    // אם הוא מכיל ערך, הדפס "Parsed: [value]"
    // אם הוא ריק, הדפס "Parse failed"
    
    
    // TODO: חלק 2 - בדוק את std::variant באמצעות Setting
    // צור משתנה מסוג Setting על סמך typeIndicator
    // אם הסוג הוא "int", אחסן את הערך כמספר שלם
    // אם הסוג הוא "double", אחסן את הערך כ-double
    // אם הטיפוס הוא "string", אחסנו את הערך כמחרוזת
    // לאחר מכן קראו ל־describeSetting והדפיסו את התוצאה
    
    
    // TODO: חלק 3 - בדקו את std::any עם DynamicStore
    // צרו DynamicStore
    // אחסנו את המחרוזת "config_loaded" תחת המפתח "status"
    // אחזרו אותה והדפיסו: "Status: [value]"
    // השתמשו ב־std::any_cast<std::string> כדי לחלץ את הערך
    
    
    return 0;
}
quiz iconבחנו את עצמכם

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

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

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