تُستخدم فضاءات الأسماء (Namespaces) لمنع التضارب الذي قد يحدث عند استخدام عدّة مكتبات في نفس البرنامج، وفضاء الاسم سابقةٌ (prefix) تعريفية للدوالّ والأصناف والأنواع وغيرها.
ما هي فضاءات الاسم؟
فضاء الاسم في لغة ++C هو مجموعة من كيانات C++ سواءً كانت دوالًا أو أصنافًا أو متغيرات، وتُسبق أسماءها باسم "فضاء الاسم"، وعند كتابة شيفرة ضمن فضاء الاسم فلا يلزم إسباق الكيانات المسمّاة التي تنتمي إلى فضاء الاسم باسم ذلك الفضاء، أمّا الكيانات الموجودة خارجه فيجب أن تُؤهّل أسماؤها (أي تُسبق باسم "فضاء الاسم").
الاسم المؤهّل يأخذ الشكل التالي <namespace>::<entity>
. انظر الشيفرة التالية:
namespace Example { const int test = 5; const int test2 = test + 12; // `Example` تعمل داخل فضاء الاسم } const int test3 = test + 3; // غير موجودة خارج فضاء الاسم `test` خطأ، لأن const int test3 = Example::test + 3; // هذا يعمل لأنّ المتغير مؤهل
فضاءات الاسم مفيدة لتجميع التعريفات المرتبطة ببعضها معًا، ويمكن تشبيهها بمراكز التسوف التي تُقسّم إلى عدة متاجر يبيع كل منها سلعًا من صنف محدد، فقد يكون أحد المتاجر متخصصًأ في الإلكترونيات بينما يكون مجال الآخر في بيع الأحذية.
هذا التصنيف المنطقي لأنواع المتاجر يساعد المتسوّقين على العثور على السلع التي يبحثون عنها. وبالمثل فإن فضاءات الاسم تساعد مبرمجي C++ في العثور على الدوالّ والأصناف والمتغيرات التي يبحثون عنها من خلال تنظيمها بطريقة منطقية. مثلًا:
namespace Electronics { int TotalStock; class Headphones { // (color, brand, model number) وصف السماعات }; class Television { // (color, brand, model number) وصف التلفاز }; } namespace Shoes { int TotalStock; class Sandal { // (color, brand, model number) وصف الصندل }; class Slipper { // (color, brand, model number) وصف الخُف }; }
لدينا فضاء اسم معرّف مسبقًا وهو فضاء الاسم العام ( global namespace)، وليس لهذا الفضاء اسم، لكن يُرمَز إليه بـ ::
. مثلًا:
void bar() { // مُعرّفة في فضاء الاسم العام } namespace foo { void bar() { // foo مُعرّفة في فضاء الاسم } void barbar() { bar(); // foo::bar() استدعاء ::bar(); // المُعرّفة في فضاء الاسم العام bar() استدعاء } }
البحث القائم على الوسائط
عند استدعاء دالّة ليس لها مؤهّلُ فضاءِ اسمٍ (namespace qualifier) صريح، فإن للمصرّف الحرية في استدعاء تلك الدالّة داخل فضاء اسم ما إن وُجد أحد أنواع المعاملات الخاصّة بتلك الدالّة في هذا الفضاء، وهذا يُسمّى "البحث القائم على الوسائط" (Argument Dependent Lookup أو ADL)، انظر:
namespace Test { int call(int i); class SomeClass {...}; int call_too(const SomeClass & data); } call(5); // خطأ: اسم دالّة غير مؤهل Test::SomeClass data; call_too(data); // ناجح.
تفشل call
لأنّ أنواع المعاملات الخاصة بها لا تنتمي إلى فضاء الاسم Test
، على عكس call_too
التي تعمل بنجاح لأنّ SomeClass
عضو في Test
، ومن ثم فإنه مؤهّل للبحث القائم على الوسائط.
ما الذي يمنع البحث القائم على الوسائط (ADL)
لا يحدث البحث القائم على الوسائط إذا كان البحث العادي غير المؤهّل (normal unqualified lookup) يجد عضوًا من صنف أو دالّة مُصرَّحة في نطاق الكتلة أو كائنًا من غير نوع الدالّة. انظر:
void foo(); namespace N { struct X {}; void foo(X ) { std::cout << '1'; } void qux(X ) { std::cout << '2'; } } struct C { void foo() {} void bar() { // لا تأخذ أي وسائط C::foo() خطأ: البحث القائم على الوسائط معطّل والدالّة foo(N::X{}); } }; void bar() { extern void foo(); // إعادة تعريف ::foo // لا تأخذ أي وسائط ::foo() خطأ: البحث القائم على الوسائط معطّل والدالّة foo(N::X{}); } int qux; void baz() { qux(N::X{}); // qux خطأ: إعلان المتغير يعطل البحث القائم على الوسائط بالنسبة لـ }
توسيع فضاءات الاسم
إحدى الميزات المفيدة لفضاءات الاسم namespace
هو أنّه يمكنك توسيعها، أي إضافة أعضاء إليها.
namespace Foo { void bar() {} } // أشياء أخرى namespace Foo { void bar2() {} }
المُوجّه 'using'
كلمة using
المفتاحية لها ثلاث نكهات، وعندما نجمعها مع كلمة namespace
فإننا نكتب موجِّهَ using
أو (Using Directive)، فإذا لم ترد أن تكتب Foo::
قبل كلّ كيان في فضاء الاسم Foo
، فيمكنك استخدام التعبير using namespace Foo;
لاستيراد كل ما تحتويه Foo
. انظر:
namespace Foo { void bar() {} void baz() {} } // Foo::bar() ينبغي استخدام Foo::bar(); // Foo استيراد using namespace Foo; bar(); // جيد baz(); // جيد
من الممكن أيضًا استيراد كيانات محدّدة في فضاء الاسم بدلاً من استيراد فضاء الاسم بأكمله:
using Foo::bar; bar(); // جيد، تم استيراده بنجاح. baz(); // خطأ، لم يُستورَد.
تحذير: غالبًا ما يُعدّ وضع using namespace
في ملفات الترويسة من الممارسات السيئة لأنّ ذلك سيؤدي إلى استيراد فضاء الاسم في كلّ ملف يتضمن تلك الترويسة. ونظرًا لعدم وجود أيّ طريقة لتعطيل استيراد فضاء الاسم، فقد يؤدي ذلك إلى تلويث فضاء الاسم (أي حشو فضاء الاسم العام برموز كثيرة أو غير متوقعة)، أو قد يؤدّي إلى التضارب بين الأسماء. انظر المثال التالي لتوضيح هذا الأمر:
/***** foo.h *****/ namespace Foo { class C; } /***** bar.h *****/ namespace Bar { class C; } /***** baz.h *****/ #include "foo.h" using namespace Foo; /***** main.cpp *****/ #include "bar.h" #include "baz.h" using namespace Bar; C c; // Foo::C و Bar::C خطأ: تضارب بين
كما ترى، فلا يمكن استخدام الموجّه using
في نطاق الصنف.
التصريح عبر using
عندما تصرّح عن كائنٍ ما باستخدام using
فسيؤدّي ذلك إلى إدخال اسم سبق تصريحه في موضع آخر إلى النطاق الحالي.
استيراد الأسماء فرديًّا من فضاء اسم
انظر المثال التالي:
#include <iostream> int main() { using std::cout; cout << "Hello, worlبd!\n"; }
عند استخدام using
لإدخال الاسم cout
من فضاء اسم std
في نطاق دالّة main
، سنتمكن من الإشارة إلى الكائن std::cout
بالاسم cout
فقط.
إعادة التصريح عن أعضاء صنف أساسي لتجنّب إخفاء الأسماء
لا يُسمح بإعادة تعريف الأعضاء خلا أعضاء الصنف الأساسي عند التصريح عن أعضاء عبر using
في نطاق الصنف. على سبيل المثال، using std::cout
غير مسموح بها في نطاق الصنف.
الاسم المُعاد تصريحه كان سيُخفى غالبًا، على سبيل المثال، تشير d1.foo
في المثال أدناه إلى Derived1::foo(const char*)
وحسب، لذلك سيحدث خطأ في التصريف.
مسألة أنّ الدالّة Base::foo(int)
ستُخفى ليس لها تأثير. لكن من الناحية الأخرى فإن d2.foo(42)
صحيحة لأنّ التصريح باستخدام using
يُدرِج Base::foo(int)
في مجموعة الكيانات المُسمّاة foo
في Derived2
. ستنتهي عملية البحث عن الأسماء بإيجاد كلا الدالّتين المُسمّاتين foo
، وبعد تحليل زيادة التحميل (overload resolution)، يُختار Base::foo
.
struct Base { void foo(int); }; struct Derived1: Base { void foo(const char * ); }; struct Derived2: Base { using Base::foo; void foo(const char * ); }; int main() { Derived1 d1; d1.foo(42); // خطأ Derived2 d2; d2.foo(42); // صحيح }
وراثة المُنشئات
الإصدار ≥ C++ 11
كحالة خاصة، يمكن أن يشير استخدام using
للتصريح في نطاق الصنف إلى مُنشئات (constructors) صنف أبٍ مباشر (direct base class)، ثم تُورَّث تلك المُنشئات إلى الصنف المشتقّ، ويمكن استخدامها لتهيئة ذلك الصنف. انظر:
struct Base { Base(int x, const char * s); }; struct Derived1: Base { Derived1(int x, const char * s): Base(x, s) {} }; struct Derived2: Base { using Base::Base; }; int main() { Derived1 d1(42, "Hello, world"); Derived2 d2(42, "Hello, world"); }
في الشيفرة أعلاه، يحتوي كل من Derived1
و Derived2
على مُنشئات تعيد توجيه الوسائط مباشرةً إلى المُنشئ المقابل للصنف Base
. وينفّذ الصنف Derived1
إعادة التوجيه بشكل صريح، بينما يستخدم الصنف Derived2
ميزة توريث المُنشئات الموجودة في C++ 11 لفعل ذلك ضمنيًا.
إنشاء فضاءات الاسم
من السهل إنشاء فضاءات الاسم، انظر المثال التالي إذ سننشئ فضاء الاسم foo
ثم نصرح عن الدالة bar
فيه:
namespace Foo { void bar() {} }
لاستدعاء bar
، يجب عليك تحديد فضاء الاسم أولاً، متبوعًا بعامل تحليل النطاق (scope resolution operator) وهو ::
:
Foo::bar();
يُسمح بإنشاء فضاء اسمٍ داخل فضاء اسم آخر، مثلًا:
namespace A { namespace B { namespace C { void bar() {} } } }
الإصدار ≥ C++ 17
يمكن تبسيط الشيفرة أعلاه على النحو التالي:
namespace A::B::C { void bar() {} }
فضاءات الاسم غير المُسمّاة أو المجهولة
يمكن استخدام فضاء اسم غير مُسمَّى (unnamed namespace) لضمان وجود ارتباط داخلي بين الأسماء التي لا يمكن الإشارة إليها إلّا بوحدة الترجمة الحالية (translation unit)، وتُعرّف فضاءات الاسم غير المسمّاة بنفس طريقة تعريف فضاءات الاسم الأخرى، ولكن من دون اسم:
namespace { int foo = 42; }
لن تكون foo
مرئيّة إلّا في وحدة الترجمة التي تظهر فيها، ويوصى بعدم استخدام فضاءات-الاسم-غير-المُسمّاة في ملفات الترويسة (Header Files) لأنّ هذا سيعطي إصدارًا من المحتوى لكل وحدة ترجمة أُدرِج فيها، وهذا مهم جدًا خاصّة عند تعريف متغيرات-عامة-غير-ثابتة. انظر:
// foo.h namespace { std::string globalString; } // 1.cpp #include "foo.h" //< تولّد: unnamed_namespace{1.cpp}::globalString ... globalString = "Initialize"; // 2.cpp #include "foo.h" //< تولّد: unnamed_namespace{2.cpp}::globalString ... std::cout << globalString; //< ستطبع دائما سلسلة نصية فارغة
فضاءات الاسم المضغوطة والمتشعبة
الإصدار ≥ C++ 17
انظر المثال التالي:
namespace a { namespace b { template < class T > struct qualifies: std::false_type {}; } } namespace other { struct bob {}; } namespace a::b { template < > struct qualifies < ::other::bob >: std::true_type {}; }
ابتداءً من C++ 17، صار بإمكانك الدخول إلى فضاء الاسم a
و كذلك b
بخطوة واحدة عبر التعبير namespace a::b
.
كنية فضاء الاسم
يمكن إعطاء فضاء الاسم كُنيةً أو اسمًا بديلًا (alias)، أي اسمًا آخر يمثّل فضاء الاسم نفسه باستخدام الصيغة namespace identifier =
، ويمكن الوصول إلى أعضاء فضاء الاسم المُكَنَّى عبر تأهيلهم (أي إسباقهم) بالكُنية.
في المثال التالي، فضاء الاسم الُمتشعّب AReallyLongName::AnotherReallyLongName
طويل جدًّا، لذا تصرح الدالّة qux
عن الكُنية N
. يمكن الآن الوصول إلى أعضاء فضاء الاسم باستخدام N::
. انظر:
namespace AReallyLongName { namespace AnotherReallyLongName { int foo(); int bar(); void baz(int x, int y); } } void qux() { namespace N = AReallyLongName::AnotherReallyLongName; N::baz(N::foo(), N::bar()); }
فضاء الاسم المضمن (Inline namespace)
الإصدار ≥ C++ 11
تُدرِج inline namespace
محتوى فضاء الاسم المُضمّن في فضاء الاسم المحيط (Enclosing)، لذلك فإنّ الشيفرة التالية:
namespace Outer { inline namespace Inner { void foo(); } }
تكافئ بشكل كبير الشيفرة أدناه:
namespace Outer { namespace Inner { void foo(); } using Inner::foo; }
غير أن عناصر Outer::Inner::
وعناصر Outer::
متطابقة، لذلك فإن السَّطرن التالين متكافئان:
Outer::foo(); Outer::Inner::foo();
لكنّ التعبير using namespace Inner;
لن يكون مكافئًا لبعض الأجزاء مثل تخصيص القوالب (template specialization):
#include <outer.h> // انظر أسفله class MyCustomType; namespace Outer { template < > void foo < MyCustomType > () { std::cout << "Specialization"; } }
يسمح فضاء الاسم المُضمَّن بتخصيص Outer::foo
، انظر المثال التالي حيث أهملنا include guard
من أجل التبسيط:
// outer.h namespace Outer { inline namespace Inner { template < typename T > void foo() { std::cout << "Generic"; } } }
بينما لا يسمح التعبير using namespace
بتخصيص Outer::foo
، انظر أدناه أيضًا حيث أهملنا include guard
من أجل التبسيط:
// outer.h namespace Outer { namespace Inner { template < typename T > void foo() { std::cout << "Generic"; } } using namespace Inner; // `Outer::foo` لا يمكن تخصيص // `Outer::Inner::foo` الصيغة الصحيحة هي }
تتيح فضاءات الاسم المُضمّنة لعدة إصدارات أن تتواجد في نفس الوقت بحيث تعود كلها افتراضيًّا إلى فضاء الاسم المُضمّن (inline
):
namespace MyNamespace { // تضمين الإصدار الأخير inline namespace Version2 { void foo(); // إصدار جديد void bar(); } namespace Version1 // الإصدار القديم { void foo(); } }
ومع استخدام:
MyNamespace::Version1::foo(); // الإصدار القديم MyNamespace::Version2::foo(); // الاصدار الجديد MyNamespace::foo(); // MyNamespace::Version1::foo(); الاصدار الافتراضي
تكنية فضاء اسم طويل
من الممكن تكنِية فضاءات الاسم الطويلة في ++C، وهو مفيد مثلًا في حال أردت الإشارة إلى مكوّنات مكتبة ما. انظر:
namespace boost { namespace multiprecision { class Number... } } namespace Name1 = boost::multiprecision; // كلا التصريحين متكافئين boost::multiprecision::Number X // كتابة فضاء الاسم بأكمله Name1::Number Y // استخدام الكنية
نطاق تصريح الكنية
يتأثّر تصريح الكنية باستخدام تعليمات using
الموجودة قبله:
namespace boost { namespace multiprecision { class Number... } } using namespace boost; // فضاءا الاسم متكافئان namespace Name1 = boost::multiprecision; namespace Name2 = multiprecision;
لكن قد تكون عمليّة تكنية فضاء الاسم مربكة أحيانًا حول اختيار الفضاء الذي تريد وضع اسم بديل أو كنية له، كما في المثال التالي:
namespace boost { namespace multiprecision { class Number... } } namespace numeric { namespace multiprecision { class Number... } } using namespace numeric; using namespace boost;
لا يُنصح بهذا لأنه ليس واضحًا إن كان Name1
يشير إلى numeric::multiprecision
أم boost::multiprecision
، نتابع:
namespace Name1 = multiprecision; // يُفضل استخدام المسار الكامل namespace Name2 = numeric::multiprecision; namespace Name3 = boost::multiprecision;
هذا الدرس جزء من سلسلة دروس عن C++.
ترجمة -بتصرّف- للفصل Chapter 44: Namespaces والفصل Chapter 46: Using declaration من كتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.