الكلمة المفتاحية Mutable
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 30 من 104.
لقد تعلمت أن الدوال الأعضاء من النوع const لا يمكنها تعديل أي متغيرات أعضاء. ولكن في بعض الأحيان تحتاج إلى تغيير عضو لا يؤثر على الحالة المنطقية للكائن - مثل ذاكرة التخزين المؤقت (cache) أو عداد الوصول. توفر الكلمة المفتاحية mutable استثناءً لقيود const.
يمكن تعديل العضو القابل للتغيير (mutable) حتى داخل دالة عضو ثابتة (const member function):
class DataFetcher {
std::string data;
mutable int accessCount; // يمكن تعديله في الدوال الثابتة (const functions)
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() ستؤدي إلى خطأ في المترجم (compiler error). تخبر الكلمة المفتاحية mutable المترجم: "يُسمح لهذا العضو بالتغيير حتى عندما يُعتبر الكائن ثابتاً (const)."
تشمل حالات الاستخدام الشائعة لـ 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-correctness) للطرق المخصصة للقراءة فقط. هذه حالة استخدام مثالية للكلمة المفتاحية mutable.
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
Document.h: قم بتعريف فئةDocumentالتي تخزن المحتوى وتتتبع إحصائيات العرض سراً. تحتاج فئتك إلى:- أعضاء خاصين (Private members):
title(string)، وcontent(string)، وmutable int viewCountيمكن تعديله حتى داخل الطرق الثابتة (const methods) - منشئ (Constructor) يأخذ العنوان والمحتوى، ويقوم بتهيئة
viewCountإلى0 - طريقة ثابتة
getTitle()تعيد العنوان - طريقة ثابتة
getContent()تزيدviewCount(مسموح بذلك لأنها mutable) وتعيد المحتوى - طريقة ثابتة
getViewCount()تعيد عدد مرات الوصول إلى المحتوى - طريقة ثابتة
getPreview()تعيد أول 20 حرفاً من المحتوى متبوعة بـ"..."— يجب ألا تزيد هذه الطريقة عدد المشاهدات لأنها مجرد معاينة
- أعضاء خاصين (Private members):
main.cpp: وضح كيف تسمح mutable بتتبع إحصائيات الوصول مع الحفاظ على الطرق ثابتة منطقياً. اقرأ عنوان المستند ومحتواه من الإدخال (كل منهما في سطر منفصل)، ثم:- أنشئ
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 references) - استدعِ
displayDocument()مع المستند الخاص بك - اطبع
"Final view count: <count>"
- أنشئ
يجب أن يكون عدد المشاهدات 0 بعد المعاينة (لأن المعاينات لا تُحسب كمشاهدات كاملة)، ثم يزداد في كل مرة يتم فيها استدعاء getContent(). الكلمة المفتاحية mutable تجعل هذا ممكناً — فبدونها، لن تتمكن من تعديل viewCount داخل الطرق الثابتة مثل getContent().
إذا كان المحتوى أقصر من 20 حرفاً، يجب أن تعيد getPreview() المحتوى بالكامل متبوعاً بـ "...".
ورقة مرجعية
تسمح الكلمة المفتاحية mutable بتعديل متغير عضو (member variable) حتى داخل الدوال الأعضاء الثابتة (const member functions):
class DataFetcher {
std::string data;
mutable int accessCount; // Can be modified in const functions
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // Allowed because accessCount is mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};بدون mutable، سيؤدي تعديل متغير عضو داخل دالة const إلى حدوث خطأ في المترجم (compiler error). تخبر هذه الكلمة المفتاحية المترجم أن هذا العضو مسموح له بالتغيير حتى عندما يُعتبر الكائن ثابتاً (const).
تشمل حالات الاستخدام الشائعة التخزين المؤقت للقيم المحسوبة (caching)، وتتبع إحصائيات الوصول، وأدوات المزامنة (synchronization primitives). يجب ألا يغير التعديل ما يمثله الكائن منطقياً:
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 بحذر - فقط للأعضاء الذين لا يؤثرون على الحالة الظاهرة (observable state) للكائن.
جرّب بنفسك
#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() مع الكائن document الخاص بك
// TODO: اطبع "Final view count: <count>"
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة