محدّدات أصناف التخزين هي كلمات مفتاحية يمكن استخدامها في التصريحات، ولا تؤثر على نوع التصريح لكنها تعدّل الطريقة التي تُخزّن بها الكيانات.
extern
محدّدُ صنف التخزين extern
يستطيع التصريحَ بإحدى الطرق الثلاث التالية، وذلك وفقًا للسياق، فمثلًا:
- يمكن استخدامه للتصريح عن متغيّر دون تعريفه، يُستخدَم عادة في ملفات الترويسات الخاصة بالمتغيّرات التي تُعرّف في ملف تنفيذ منفصل.
// نطاق عام int x; // بالقيمة الافتراضية x تعريف: سيهيأ extern int y; // معرَّف في مكان آخر، غالبًا في وحدة ترجمة أخرى y :تصريح. extern int z = 42; // أيّ تأثير هنا "extern" تعريف: ليس للكلمة المفتاحية
-
إعطاء ارتباط خارجي (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; // ارتباط داخلي لأنّه في فضاء اسم غير مُسمّى }
- إعادة التصريح (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
ثلاث معانٍ مختلفة.
- يعطي ارتباطًا داخليًا لمتغيّر أو دالّة مُصرَّح عنها في نطاق فضاء الاسم.
// دالة داخلية، لا يمكن الارتباط بها 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)); }
-
تصرّح بأنّ المتغيّر له مدة تخزين ساكنة - static storage duration - (ما لم تكن خيطًا محليًا
thread_local
)، ومتغيرات نطاق فضاء الاسم تكون ساكنة ضمنيًا، وتُهيّأ المتغيّرات المحلية الساكنة مرّة واحدة فقط، إذ يمرّ التحكّم في المرّة الأولى عبر تعريفها، ولا تُدمَّر بعد الخروج من نطاقها.
void f() { static int count = 0; std::cout << "f has been called " << ++count << " times so far\n"; }
- عند تطبيقها على تصريح عن عضو صنف (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
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.