هل الكلمة الأساسية "قابلة للتغيير" لها أي غرض آخر غير السماح بتعديل المتغير بواسطة دالة const؟

StackOverflow https://stackoverflow.com/questions/105014

  •  01-07-2019
  •  | 
  •  

سؤال

منذ فترة، عثرت على بعض التعليمات البرمجية التي تميز متغير عضو في فئة ما بامتداد mutable الكلمة الرئيسية.بقدر ما أستطيع أن أرى أنه يسمح لك ببساطة بتعديل متغير في ملف const طريقة:

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};

هل هذا هو الاستخدام الوحيد لهذه الكلمة الرئيسية أم أن هناك ما هو أكثر مما تراه العين؟لقد استخدمت هذه التقنية منذ ذلك الحين في الفصل الدراسي، ووضع علامة على boost::mutex كما يسمح للتغيير const وظائف لقفله لأسباب تتعلق بسلامة الخيط، ولكن بصراحة، يبدو الأمر وكأنه نوع من الاختراق.

هل كانت مفيدة؟

المحلول

فهو يسمح بالتمييز بين const وconst المنطقية.تحدث الثبات المنطقي عندما لا يتغير الكائن بطريقة مرئية من خلال الواجهة العامة، مثل مثال القفل الخاص بك.مثال آخر هو الفصل الذي يحسب القيمة في المرة الأولى التي يتم طلبها فيها، ويقوم بتخزين النتيجة مؤقتًا.

منذ ج ++ 11 mutable يمكن استخدامها على لامدا للإشارة إلى أن الأشياء التي تم التقاطها حسب القيمة قابلة للتعديل (وهي ليست قابلة للتعديل بشكل افتراضي):

int x = 0;
auto f1 = [=]() mutable {x = 42;};  // OK
auto f2 = [=]()         {x = 42;};  // Error: a by-value capture cannot be modified in a non-mutable lambda

نصائح أخرى

ال mutable الكلمة الرئيسية هي وسيلة لاختراق const الحجاب الذي تسدله على أغراضك.إذا كان لديك مرجع أو مؤشر ثابت لكائن ما، فلا يمكنك تعديل هذا الكائن بأي شكل من الأشكال يستثني متى وكيف يتم وضع علامة عليه mutable.

مع الخاص بك const مرجع أو مؤشر أنت مقيد به:

  • حق الوصول للقراءة فقط لأي أعضاء بيانات مرئيين
  • إذن للاتصال فقط بالطرق التي تم وضع علامة عليها const.

ال mutable يجعل الاستثناء من ذلك أنه يمكنك الآن كتابة أو تعيين أعضاء البيانات الذين تم وضع علامة عليهم mutable.هذا هو الفرق الوحيد المرئي من الخارج.

داخليا تلك const يمكنك أيضًا الكتابة إلى الأساليب المرئية لأعضاء البيانات الذين تم وضع علامة عليهم mutable.في الأساس يتم ثقب الحجاب الثابت بشكل شامل.الأمر متروك تمامًا لمصمم واجهة برمجة التطبيقات (API) للتأكد من ذلك mutable لا يدمر const المفهوم ويستخدم فقط في حالات خاصة مفيدة.ال mutable تساعد الكلمة الرئيسية لأنها تحدد بوضوح أعضاء البيانات الخاضعين لهذه الحالات الخاصة.

في الممارسة العملية يمكنك استخدامها const بقلق شديد في جميع أنحاء قاعدة التعليمات البرمجية الخاصة بك (تريد بشكل أساسي "إصابة" قاعدة التعليمات البرمجية الخاصة بك بـ const "مرض").في هذا العالم المؤشرات والمراجع هي const مع استثناءات قليلة جدًا، يتم إنتاج تعليمات برمجية يسهل التفكير فيها وفهمها.للحصول على استطراد مثير للاهتمام، ابحث عن "الشفافية المرجعية".

بدون ال mutable الكلمة الرئيسية التي ستضطر في النهاية إلى استخدامها const_cast للتعامل مع مختلف الحالات الخاصة المفيدة التي يسمح بها (التخزين المؤقت، عد المراجع، تصحيح البيانات، وما إلى ذلك).للأسف const_cast هو أكثر تدميرا بكثير من mutable لأنه يفرض API عميل لتدمير const حماية الأشياء (الأشياء) التي يستخدمها.كما أنه يسبب انتشارا واسعا const دمار: const_castيسمح مؤشر أو مرجع ثابت بالكتابة غير المقيدة وأسلوب استدعاء الوصول إلى الأعضاء المرئيين.في المقابل mutable يتطلب من مصمم واجهة برمجة التطبيقات (API) ممارسة التحكم الدقيق في const الاستثناءات، وعادةً ما تكون هذه الاستثناءات مخفية const طرق العمل على البيانات الخاصة.

(ملاحظة:أشير إلى البيانات والطريقة الرؤية بعض الاوقات.أنا أتحدث عن الأعضاء الذين تم وضع علامة عليهم على أنهم عامون مقابل أعضاء.خاص أو محمي وهو نوع مختلف تمامًا من حماية الكائنات التي تمت مناقشتها هنا.)

إن استخدامك مع Boost::mutex هو بالضبط ما تهدف إليه هذه الكلمة الرئيسية.الاستخدام الآخر هو التخزين المؤقت للنتائج الداخلية لتسريع الوصول.

في الأساس، ينطبق "قابل للتغيير" على أي سمة فئة لا تؤثر على الحالة المرئية خارجيًا للكائن.

في نموذج التعليمات البرمجية في سؤالك، قد يكون قابل للتغيير غير مناسب إذا كانت قيمة Done_ تؤثر على الحالة الخارجية، فهذا يعتمد على ما هو موجود في ...؛جزء.

Mutable مخصص لوضع علامة على سمة معينة على أنها قابلة للتعديل من الداخل const طُرق.وهذا هو غرضها الوحيد.فكر جيدًا قبل استخدامه، لأن الكود الخاص بك سيكون على الأرجح أكثر وضوحًا وأكثر قابلية للقراءة إذا قمت بتغيير التصميم بدلاً من استخدامه mutable.

http://www.highprogrammer.com/alan/rants/mutable.html

لذلك إذا لم يكن الجنون أعلاه هو ما هو قابل للتغيير ، فما هو؟ها هي الحالة الدقيقة:قابلة للتغيير للحالة التي يكون فيها كائن ثابتًا منطقياً ، ولكن في الممارسة العملية يحتاج إلى تغيير.هذه الحالات قليلة ومتباعدة ، لكنها موجودة.

تتضمن الأمثلة التي قدمها المؤلف متغيرات التخزين المؤقت والتصحيح المؤقت.

إنه مفيد في المواقف التي تكون فيها الحالة الداخلية مخفية مثل ذاكرة التخزين المؤقت.على سبيل المثال:

class HashTable
{
...
public:
    string lookup(string key) const
    {
        if(key == lastKey)
            return lastValue;

        string value = lookupInternal(key);

        lastKey = key;
        lastValue = value;

        return value;
    }

private:
    mutable string lastKey, lastValue;
};

ومن ثم يمكنك الحصول على const HashTable الكائن لا يزال يستخدمه lookup() الطريقة التي تعدل ذاكرة التخزين المؤقت الداخلية.

حسنا، نعم، هذا ما يفعله.أستخدمه للأعضاء الذين تم تعديلهم بطرق لا تفعل ذلك منطقيا تغيير حالة الفئة - على سبيل المثال، لتسريع عمليات البحث عن طريق تنفيذ ذاكرة التخزين المؤقت:

class CIniWrapper
{
public:
   CIniWrapper(LPCTSTR szIniFile);

   // non-const: logically modifies the state of the object
   void SetValue(LPCTSTR szName, LPCTSTR szValue);

   // const: does not logically change the object
   LPCTSTR GetValue(LPCTSTR szName, LPCTSTR szDefaultValue) const;

   // ...

private:
   // cache, avoids going to disk when a named value is retrieved multiple times
   // does not logically change the public interface, so declared mutable
   // so that it can be used by the const GetValue() method
   mutable std::map<string, string> m_mapNameToValue;
};

الآن، يجب عليك استخدام هذا بحذر - تعد مشكلات التزامن مصدر قلق كبير، حيث قد يفترض المتصل أنه آمن لمؤشر الترابط إذا كان يستخدم فقط const طُرق.وبالطبع التعديل mutable لا ينبغي أن تغير البيانات سلوك الكائن بأي طريقة مهمة، وهو أمر يمكن انتهاكه بالمثال الذي قدمته، على سبيل المثال، إذا كان من المتوقع أن تكون التغييرات المكتوبة على القرص مرئية على الفور للتطبيق.

mutable موجود كما تستنتج للسماح لأحد بتعديل البيانات في وظيفة ثابتة بخلاف ذلك.

القصد هو أنه قد يكون لديك وظيفة "لا تفعل شيئًا" للحالة الداخلية للكائن، ومن ثم تقوم بوضع علامة على الوظيفة const, ، ولكن قد تحتاج حقًا إلى تعديل بعض حالات الكائنات بطرق لا تؤثر على وظائفها الصحيحة.

قد تكون الكلمة الأساسية بمثابة تلميح للمترجم - يمكن للمترجم النظري وضع كائن ثابت (مثل عمومي) في الذاكرة التي تم وضع علامة عليها للقراءة فقط.حضور ال mutable يلمح إلى أنه لا ينبغي القيام بذلك.

فيما يلي بعض الأسباب الصحيحة للإعلان عن البيانات القابلة للتغيير واستخدامها:

  • سلامة الخيط.يعلن أ mutable boost::mutex معقول تماما.
  • إحصائيات.حساب عدد الاستدعاءات للدالة، مع الأخذ بعين الاعتبار بعض أو كل وسيطاتها.
  • الحفظ.حساب بعض الإجابات الباهظة الثمن، ثم تخزينها للرجوع إليها في المستقبل بدلاً من إعادة حسابها مرة أخرى.

يتم استخدام Mutable عندما يكون لديك متغير داخل الفئة والذي يتم استخدامه فقط داخل تلك الفئة للإشارة إلى أشياء مثل كائن المزامنة (mutex) أو القفل على سبيل المثال.لا يغير هذا المتغير سلوك الفئة، ولكنه ضروري لتنفيذ أمان مؤشر الترابط للفئة نفسها.وبالتالي، إذا لم يكن هناك "قابل للتغيير"، فلن تتمكن من الحصول على وظائف "const" لأن هذا المتغير سيحتاج إلى التغيير في جميع الوظائف المتاحة للعالم الخارجي.لذلك، تم تقديم قابل للتغيير من أجل جعل متغير العضو قابلاً للكتابة حتى بواسطة دالة const.

يُبلغ المحدد المحدد كل من المترجم والقارئ أنه من الآمن ومتوقع أن يتم تعديل متغير عضو داخل وظيفة عضو CONST.

يستخدم Mutable بشكل أساسي في تفاصيل تنفيذ الفصل.لا يحتاج مستخدم الفصل إلى معرفة ذلك، وبالتالي فإن الطريقة التي يعتقد أن "ينبغي" أن تكون ثابتة يمكن أن تكون كذلك.مثالك على أن يكون كائن المزامنة قابلاً للتغيير هو مثال قانوني جيد.

استخدامك له ليس اختراقًا، على الرغم من أنه مثل العديد من الأشياء في C++، قابل للتغيير يستطيع كن مخترقًا للمبرمج الكسول الذي لا يريد العودة إلى الوراء ووضع علامة على شيء لا ينبغي أن يكون ثابتًا على أنه غير ثابت.

استخدم "قابل للتغيير" عندما يتعلق الأمر بالأشياء التي تعتبر عديمة الحالة منطقيًا للمستخدم (وبالتالي يجب أن تحتوي على حروف "const" في واجهات برمجة التطبيقات للفئة العامة) ولكنها ليست عديمة الحالة في التنفيذ الأساسي (الكود الموجود في .cpp الخاص بك).

الحالات التي أستخدمها بشكل متكرر هي التهيئة البطيئة لأعضاء "البيانات القديمة البسيطة" بدون حالة.وهي مثالية في الحالات الضيقة عندما يكون إنشاء (معالج) أو حمل (ذاكرة) هؤلاء الأعضاء مكلفًا للغاية ولن يطلبهم العديد من مستخدمي الكائن أبدًا.في هذه الحالة، تريد إنشاءًا بطيئًا على الواجهة الخلفية من أجل الأداء، نظرًا لأن 90% من الكائنات المبنية لن تحتاج أبدًا إلى بنائها على الإطلاق، ومع ذلك لا تزال بحاجة إلى تقديم واجهة برمجة التطبيقات (API) الصحيحة عديمة الحالة للاستهلاك العام.

متغير يغير معنى const من const bitwise إلى const المنطقية للفئة.

وهذا يعني أن الفئات التي تحتوي على أعضاء قابلين للتغيير تعد ثابتة ولن تظهر بعد الآن في أقسام القراءة فقط من الملف القابل للتنفيذ.

علاوة على ذلك، فهو يعدل فحص النوع عن طريق السماح const وظائف الأعضاء لتغيير الأعضاء القابلين للتغيير دون استخدام const_cast.

class Logical {
    mutable int var;

public:
    Logical(): var(0) {}
    void set(int x) const { var = x; }
};

class Bitwise {
    int var;

public:
    Bitwise(): var(0) {}
    void set(int x) const {
        const_cast<Bitwise*>(this)->var = x;
    }
};

const Logical logical; // Not put in read-only.
const Bitwise bitwise; // Likely put in read-only.

int main(void)
{
    logical.set(5); // Well defined.
    bitwise.set(5); // Undefined.
}

راجع الإجابات الأخرى للحصول على مزيد من التفاصيل ولكني أردت تسليط الضوء على أن الأمر لا يتعلق فقط بسلامة النوع وأنه يؤثر على النتيجة المجمعة.

في بعض الحالات (مثل التكرارات سيئة التصميم)، يحتاج الفصل إلى الاحتفاظ بعدد أو بعض القيمة العرضية الأخرى، والتي لا تؤثر حقًا على "الحالة" الرئيسية للفئة.هذا هو غالبًا المكان الذي أرى فيه قابلاً للتغيير.بدون التغيير، ستضطر إلى التضحية بكامل ثبات تصميمك.

يبدو الأمر وكأنه اختراق في معظم الأوقات بالنسبة لي أيضًا.مفيد في حالات قليلة جداً.

المثال الكلاسيكي (كما هو مذكور في الإجابات الأخرى) والموقف الوحيد الذي رأيته هو mutable الكلمة الأساسية المستخدمة حتى الآن هي للتخزين المؤقت لنتيجة معقدة Get الطريقة، حيث يتم تنفيذ ذاكرة التخزين المؤقت كعضو بيانات في الفئة وليس كمتغير ثابت في الطريقة (لأسباب المشاركة بين عدة وظائف أو النظافة البسيطة).

بشكل عام، بدائل استخدام mutable عادةً ما تكون الكلمة الأساسية متغيرًا ثابتًا في الطريقة أو const_cast حيلة.

هناك شرح مفصل آخر في هنا.

يمكن أن يكون التغيير مفيدًا عندما تقوم بتجاوز دالة افتراضية const وترغب في تعديل متغير عضو الفئة التابعة لديك في تلك الوظيفة.في معظم الحالات، لن ترغب في تغيير واجهة الفئة الأساسية، لذا يتعين عليك استخدام متغير عضو قابل للتغيير خاص بك.

تعد الكلمة الأساسية القابلة للتغيير مفيدة جدًا عند إنشاء بذرة لأغراض اختبار الفصل.يمكنك إيقاف وظيفة const وستظل قادرًا على زيادة العدادات (القابلة للتغيير) أو أي وظيفة اختبار قمت بإضافتها إلى كعب الروتين الخاص بك.يؤدي هذا إلى الحفاظ على واجهة الفئة المتوقفة سليمة.

أحد أفضل الأمثلة التي نستخدم فيها قابلية التغيير هو النسخة العميقة.في منشئ نسخة نرسل const &obj كحجة.وبالتالي فإن الكائن الجديد الذي تم إنشاؤه سيكون من النوع الثابت.إذا أردنا تغيير (في الغالب لن نغير، في حالات نادرة قد نغير) الأعضاء في هذا الكائن الثابت الذي تم إنشاؤه حديثًا، نحتاج إلى إعلانه كـ mutable.

mutable يمكن استخدام فئة التخزين فقط على عضو بيانات غير ثابت غير ثابت في الفصل.يمكن تعديل عضو البيانات القابل للتغيير في الفصل حتى لو كان جزءًا من كائن تم الإعلان عنه على أنه const.

class Test
{
public:
    Test(): x(1), y(1) {};
    mutable int x;
    int y;
};

int main()
{
    const Test object;
    object.x = 123;
    //object.y = 123;
    /* 
    * The above line if uncommented, will create compilation error.
    */   

    cout<< "X:"<< object.x << ", Y:" << object.y;
    return 0;
}

Output:-
X:123, Y:1

في المثال أعلاه، نحن قادرون على تغيير قيمة متغير العضو x على الرغم من أنه جزء من كائن تم الإعلان عنه على أنه const.وذلك لأن المتغير x تم إعلانه على أنه قابل للتغيير.ولكن إذا حاولت تعديل قيمة متغير العضو y, ، سوف يلقي المترجم خطأ.

الكلمة الأساسية "قابل للتغيير" هي في الواقع كلمة رئيسية محجوزة. غالبًا ما يتم استخدامها لتغيير قيمة المتغير الثابت. إذا كنت تريد الحصول على قيم متعددة للثابت، فاستخدم الكلمة الأساسية القابلة للتغيير.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top