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 רק כשאתם באמת זקוקים לגמישות בסוג בזמן ריצה.
אתגר
קלבואו נבנה מערכת תצורה שמדגימה את העוצמה של טיפוסי אוצר מילים מודרניים ב־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]
- עבור int:
DynamicStore.h: צרו מאגר פשוט של מפתח-ערך באמצעותstd::any.הגדירו מחלקה
DynamicStoreשיכולה לאחסן ערכים מכל טיפוס. עליה לכלול:- מתודה
setשמקבלת מפתח מסוג מחרוזת וערך מסוגstd::any, ומאחסנת אותם פנימית (השתמשו ב־std::map) - מתודה
getשמקבלת מפתח ומחזירה את ערך ה־std::any(החזירוstd::anyריק אם המפתח לא קיים) - מתודה
hasKeyשמחזירה true אם המפתח קיים
- מתודה
main.cpp: קראו שלושה קלטים:- מחרוזת שעשויה להיות מספר (לבדיקת
std::optional) - ציון טיפוס:
int,doubleאוstring - ערך שמתאים לטיפוס הזה
הדגימו את כל שלושת טיפוסי אוצר המילים:
ראשית, השתמשו בפונקציה
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;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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++ אונליין