מילת המפתח mutable
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 30 מתוך 104.
למדת שפונקציות איבר const אינן יכולות לשנות משתני איבר כלשהם. אבל לפעמים צריך לשנות איבר שאינו משפיע על המצב הלוגי של האובייקט — כמו מטמון או מונה גישות. מילת המפתח mutable מספקת חריג להגבלת const.
ניתן לשנות איבר mutable אפילו בתוך פונקציית איבר const:
class DataFetcher {
std::string data;
mutable int accessCount; // ניתן לשנות בפונקציות const
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // מותר כי accessCount הוא mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};ללא mutable, הגדלת accessCount בתוך הפונקציה הקבועה getData() תגרום לשגיאת קומפילציה. מילת המפתח mutable אומרת למהדר: "מותר לשנות את האיבר הזה גם כשהאובייקט נחשב קבוע."
מקרי שימוש נפוצים ב-mutable כוללים שמירה במטמון של ערכים מחושבים, מעקב אחר נתונים סטטיסטיים של גישה ופרימיטיבים לסנכרון כמו mutexes. בכל אחד מהמקרים, השינוי אינו משנה את מה שהאובייקט מייצג מבחינה לוגית - ערך שמור במטמון הוא רק אופטימיזציה, ומונה גישות הוא מטא-נתון על השימוש.
class Circle {
double radius;
mutable double cachedArea;
mutable bool areaCached;
public:
double getArea() const {
if (!areaCached) {
cachedArea = 3.14159 * radius * radius;
areaCached = true;
}
return cachedArea;
}
};השתמשו ב־mutable במשורה — יש להחיל אותו רק על איברים שאינם משפיעים על המצב הנצפה של האובייקט.
אתגר
קלבואו נבנה מציג מסמכים שעוקב אחר מספר הפעמים שבהן צפו במסמך, בלי לפגוע בנכונות ה-const של המתודות לקריאה בלבד שלנו. זוהי דוגמה מושלמת לשימוש במילת המפתח mutable.
תיצרו שני קבצים כדי לארגן את הקוד שלכם:
Document.h: הגדירו מחלקה בשםDocumentששומרת תוכן ועוקבת בחשאי אחר נתוני הצפייה. המחלקה שלכם צריכה לכלול:- חברים פרטיים:
title(מחרוזת),content(מחרוזת), ו-mutable int viewCountשאפשר לשנות אפילו במתודות const - בנאי שמקבל כותרת ותוכן ומאתחל את
viewCountל-0 - מתודת const בשם
getTitle()שמחזירה את הכותרת - מתודת const בשם
getContent()שמגדילה אתviewCount(הדבר מותר כי הוא mutable) ומחזירה את התוכן - מתודת const בשם
getViewCount()שמחזירה את מספר הפעמים שבהן ניגשו לתוכן - מתודת const בשם
getPreview()שמחזירה את 20 התווים הראשונים של התוכן ואחריהם"...". מונה הצפיות לא אמור לגדול בעקבות זאת, כי מדובר בתצוגה מקדימה בלבד
- חברים פרטיים:
main.cpp: הדגימו כיצד mutable מאפשר לעקוב אחר נתוני גישה תוך שמירה על כך שהמתודות יהיו const מבחינה לוגית. קראו מהקלט כותרת ותוכן של מסמך (כל אחד בשורה נפרדת), ואז:- צרו
Documentעם ערכי הקלט - הדפיסו
"Title: <title>" - הדפיסו
"Preview: <preview>" - הדפיסו
"Views after preview: <count>" - גשו לתוכן המלא באמצעות
getContent()והדפיסו"Content: <content>" - הדפיסו
"Views after first read: <count>" - גשו שוב לתוכן והדפיסו
"Content: <content>" - הדפיסו
"Views after second read: <count>" - צרו פונקציית עזר
void displayDocument(const Document& doc)שמדפיסה"[Const Access] <title>: <content>". כך תוכלו להוכיח שמתודות ה-const שלנו פועלות עם הפניות const - קראו ל-
displayDocument()עם המסמך שלכם - הדפיסו
"Final view count: <count>"
- צרו
מספר הצפיות צריך להיות 0 אחרי התצוגה המקדימה (מכיוון שתצוגות מקדימות אינן נחשבות לצפיות מלאות), ואז לגדול בכל פעם שקוראים ל-getContent(). מילת המפתח mutable מאפשרת זאת; בלעדיה, לא הייתם יכולים לשנות את viewCount בתוך מתודות const כמו getContent().
אם התוכן קצר מ-20 תווים, getPreview() צריכה להחזיר את התוכן כולו ואחריו "...".
נסו בעצמכם
#include <iostream>
#include <string>
#include "Document.h"
using namespace std;
// TODO: לממש את הפונקציה displayDocument
// היא צריכה להדפיס: "[Const Access] <title>: <content>"
void displayDocument(const Document& doc) {
// TODO: לממש את הפונקציה הזו
}
int main() {
// קריאת קלט
string title;
string content;
getline(cin, title);
getline(cin, content);
// TODO: ליצור Document עם ערכי הקלט
// TODO: להדפיס "Title: <title>"
// TODO: להדפיס "Preview: <preview>"
// TODO: להדפיס "Views after preview: <count>"
// TODO: לגשת לתוכן המלא ולהדפיס "Content: <content>"
// TODO: להדפיס "Views after first read: <count>"
// TODO: לגשת שוב לתוכן ולהדפיס "Content: <content>"
// TODO: להדפיס "Views after second read: <count>"
// TODO: לקרוא ל־displayDocument() עם המסמך שלך
// TODO: להדפיס "Final view count: <count>"
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 ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין