Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

الكلمة المفتاحية 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 باعتدال - يجب تطبيقها فقط على الأعضاء التي لا تؤثر على الحالة الملحوظة للكائن.

challenge icon

التحدي

سهل

لنقم ببناء عارض مستندات يتتبع عدد مرات عرض المستند — دون كسر صحة الثوابت (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 حرفاً من المحتوى متبوعة بـ "..." — يجب ألا تزيد هذه الطريقة عدد المشاهدات لأنها مجرد معاينة
  • 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;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming