سلسلة ++c للمحترفين محددات أصناف التخزين (Storage class specifiers) وأمثلة على استخدامها في Cpp


محمد الميداوي

محدّدات أصناف التخزين هي كلمات مفتاحية يمكن استخدامها في التصريحات، ولا تؤثر على نوع التصريح لكنها تعدّل الطريقة التي تُخزّن بها الكيانات.

extern

محدّدُ صنف التخزين ‎extern‎ يستطيع التصريحَ بإحدى الطرق الثلاث التالية، وذلك وفقًا للسياق، فمثلًا:

  1. يمكن استخدامه للتصريح عن متغيّر دون تعريفه، يُستخدَم عادة في ملفات الترويسات الخاصة بالمتغيّرات التي تُعرّف في ملف تنفيذ منفصل.
// نطاق عام
int x; // بالقيمة الافتراضية x تعريف: سيهيأ
extern int y; // معرَّف في مكان آخر، غالبًا في وحدة ترجمة أخرى y :تصريح.
extern int z = 42; // أيّ تأثير هنا "extern" تعريف: ليس للكلمة المفتاحية
  1. إعطاء ارتباط خارجي (external linkage) لمتغيّر في نطاق فضاء الاسم، حتى لو كانت الكلمتان المفتاحيتان ‎const‎ و ‎constexpr‎ تتسبّبان في إنشاء ارتباط داخلي.
// النطاق العام
const int w = 42; // C وخارجي في ،C++ ارتباط داخلي في
static
const int x = 42; // C++ و C ارتباط داخلي في كل من
extern
const int y = 42; // C++ و C ارتباط خارجي في كل من
namespace {
    extern
    const int z = 42; // ارتباط داخلي لأنّه في فضاء اسم غير مُسمّى
}
  1. إعادة التصريح (redeclaring) عن متغيّر في نطاق الكتلة إن صُرِّح عنه مسبقًا عبر الارتباط (linkage). أما خلاف ذلك، سيُصرّح عن متغيّر جديد عبر الارتباط، وسيكون عضوًا في أقرب فضاء اسم محيط.
// النطاق العام
namespace {
    int x = 1;
    struct C {
        int x = 2;
        void f() {
            extern int x; // x إعادة التصريح عن
            std::cout << x << '\n'; // طباعة 1 وليس 2
        }
    };
}
void g() {
    extern int y; // العامّة المُعرّفة في موضع آخر y ارتباط خارجي، ويشير إلى قيمة y لدى
}

يمكن أيضًا التصريح عن الدالّة على أنها ‎extern‎، لكنّ هذا ليس له أيّ تأثير وإنّما يُستخدم في العادةً كتلميح للقارئ بأنّ الدالّة المُصرَّح عنها هنا ستعُرَّف في وحدة ترجمة أخرى. ففي المثال التالي، يكون ()void f تصريحًا لاحقًا يعني أن f ستُعرَّف في وحدة الترجمة هذه، أما ()extern void g ليس تصريحًا لاحقًا، مما يعني أن g تُعرَّف في وحدة ترجمة أخرى، انظر:

void f(); 
extern void g(); 

في الشيفرة أعلاه، في حال التصريح عن ‎f‎ مع ‎extern‎ والتصريح عن ‎g‎ بدون ‎extern‎، لن يؤثر ذلك على صحة أو دلالات البرنامج، ولكن من المحتمل أن يربك القارئ.

register

الإصدار < C++‎ 17

++‎>

register هو محدّد صنف تخزين يخبر االمُصرِّف أنّ المتغيّر سيُستخدَم بكثرة، وتأتي الكلمة "register" من حقيقة أن المُصرِّف قد يختار تخزين هذا المتغيّر في سجل وحدة المعالجة المركزية (CPU register) لتسريع الوصول إليه. وقد أُهمِلت بدءًا من الإصدار C++‎ 11.

register int i = 0;
while (i < 100) {
    f(i);
    int g = i * i;
    i += h(i, g);
}

يُمكن تعريف كلٍّ من المتغيّرات المحلية ومُعامِلات الدوالّ بالكلمة المفتاحية ‎register‎، ولا تضع C++‎ قيودًا على ما يمكن فعله بالمتغيّرات المعرّفة بـ ‎register‎، على عكس لغة C. فكثلًا، يجوز أخذ عنوان متغيّر مصرّح عنه بـ ‎register‎، لكنّ هذا قد يمنع المُصرِّف من تخزين مثل هذا المتغيّر في السجل (register).

الإصدار ≥ C++‎ 17

الكلمة المفتاحية ‎register‎ غير مستخدمة ومحفوظة، ولا ينبغي استخدامها.

static

لدى محدّد التخزين ‎static‎ ثلاث معانٍ مختلفة.

  1. يعطي ارتباطًا داخليًا لمتغيّر أو دالّة مُصرَّح عنها في نطاق فضاء الاسم.
// دالة داخلية، لا يمكن الارتباط بها
static double semiperimeter(double a, double b, double c) {
    return (a + b + c) / 2.0;
}
// التصدير إلى العميل
double area(double a, double b, double c) {
    const double s = semiperimeter(a, b, c);
    return sqrt(s * (s - a) * (s - b) * (s - c));
}
  1. تصرّح بأنّ المتغيّر له مدة تخزين ساكنة - static storage duration - (ما لم تكن خيطًا محليًا ‎thread_local‎)، ومتغيرات نطاق فضاء الاسم تكون ساكنة ضمنيًا، وتُهيّأ المتغيّرات المحلية الساكنة مرّة واحدة فقط، إذ يمرّ التحكّم في المرّة الأولى عبر تعريفها، ولا تُدمَّر بعد الخروج من نطاقها.
void f() {
    static int count = 0;
    std::cout << "f has been called " << ++count << " times so far\n";
}
  1. عند تطبيقها على تصريح عن عضو صنف (class member)، فإنّها تجعل ذلك العضو ساكنًا.
struct S {
    static S* create() {
        return new S;
    }
};
int main() {
    S* s = S::create();
}

لاحظ أنّه إن كان العضو ساكنًا، ستَنطبق النقطتان 2 و 3 في نفس الوقت، إذ تضع الكلمة المفتاحية ‎static‎ عضو الصنف في عضو بيانات ساكن (static data member)، كما تجعله أيضًا في متغيّر ذي مدة تخزين ساكنة (static storage duration).

auto

الإصدار ≤ C++‎ 03

يصرّح هذا المحدّد بأنّ المتغيّر لديه مدة تخزين آلية (automatic storage duration)، وهو غير ضروري نظرًا لأنّ مدة التخزين التلقائي هي الإعداد الافتراضي في نطاق الكتلة، كما لا يُسمح باستخدام المحدّد auto في نطاق فضاء الاسم.

void f() {
    auto int x;
    auto y;
auto int z; 

في المثال السابق، auto int x تكافئ int x، ولا تجوز auto y في ++C لكنها جائزة في C89، كذلك لا تجوز auto int z، إذ لا يمكن أن تكون متغيرات نطاق فضاء الاسم آلية.

تغيّر معنى ‎auto‎ في الإصدار C++‎ 11 بشكل كامل، ولم تعد محدّدًا للتخزين، وإنّما صارت تُستخدم في استنتاج الأنواع.

mutable

mutable هو محدّدٌ يمكن تطبيقه على تصريح عضو بيانات من صنف غير ساكن (non-static) وغير مرجعي (non-reference)، والعضو القابل للتغيير في صنفٍ لا يكون غيرَ ثابتٍ حتى لو كان الكائن ثابتًا.

class C {
    int x;
    mutable int times_accessed;
    public:
        C(): x(0), times_accessed(0) {}
    int get_x() const {
        ++times_accessed; // لا بأس: يمكن للدوال التابعة الثابتة أن تعدّل أعضاء البيانات غير الثابتة
        return x;
    }
    void set_x(int x) {
        ++times_accessed;
        this -> x = x;
    }
};

الإصدار ≥ C++‎ 11

أضيف معنى ثانٍ للكلمة المفتاحية ‎mutable‎ في C++‎ 11، فعندما تتبع قائمة المُعاملات الخاصة بتعبير لامدا، فإنها تقمع المؤهّل الثباتي ‎const‎ الضمني الموجود في مُعامل استدعاء لامدا (lambda's function call operator). ولذلك يمكن لتعابير لامدا القابلة للتغيير (mutable) أنّ تعدّل قيم الكيانات التي حصلت عليها عن طريق النسخ.

std::vector < int > my_iota(int start, int count) {
    std::vector < int > result(count);
    std::generate(result.begin(), result.end(),
        [start]() mutable {
            return start++;
        });
    return result;
}

لاحظ أنّ ‎mutable‎ لا تُعدُّ محدّدَ صنف تخزين إذا استخدِمَت بهذه الطريقة لتشكيل تعابير لامدا تقبل للتغيير (أي mutable).

هذا الدرس جزء من سلسلة دروس عن C++‎.

ترجمة -بتصرّف- للفصل Chapter 133: Storage class specifiers من كتاب C++ Notes for Professionals





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن