
ملخص الأجزاء السابقة
نظرًا للقيود المفروضة على القدرة على استخدام برامج التحويل البرمجي لـ C ++ 11 ، وبسبب عدم وجود بدائل ، أراد التعزيز كتابة تطبيقه الخاص لمكتبة C ++ 11 القياسية أعلى مكتبة C ++ 98 / C ++ 03 المزودة مع برنامج التحويل البرمجي.
بالإضافة إلى ملفات الرأس القياسية
type_traits ، تمت إضافة
مؤشر الترابط ،
mutex ،
chrono ،
nullptr.h الذي يطبق
std :: nullptr_t و
core.h حيث تمت إضافة وحدات الماكرو المتعلقة
بالوظائف المعتمدة على المترجم ، بالإضافة إلى توسيع المكتبة القياسية.
رابط إلى GitHub بالنتيجة لهذا اليوم للصبر وغير القراء:
نرحب بالالتزامات والنقد البناء
جدول المحتويات
مقدمةالفصل 1. فيام الإشراف على vadensالفصل 2. #ifndef __CPP11_SUPPORT__ # تعريف __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifالفصل 3. إيجاد تنفيذ nullptr الكمالالفصل 4. C ++ قالب سحري....
4.1 نبدأ صغيرة....
4.2 حول عدد الأخطاء المعجزة التي يجمعها لنا السجل....
4.3 المؤشرات والجميع....
4.4 ما هو مطلوب أيضًا لمكتبة النماذجالفصل الخامس
...
الفصل 2. #ifndef __CPP11_SUPPORT__ # تعريف __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
بعد أن تم تمشيط كل الشفرة قليلاً وتقسيمها بواسطة رؤوس "قياسية" إلى
مساحة اسم منفصلة
stdex ، شرعت في ملء
type_traits و
nullptr.h وعلى طول نفس
المركز .
h ، والذي يحتوي على وحدات ماكرو لتحديد إصدار المعيار الذي يستخدمه المترجم ودعمه
nullptr الأصلي و
char16_t و
char32_t و
static_assert .
من الناحية النظرية ، كل شيء بسيط - وفقًا لمعيار C ++
(البند 14.8) ، يجب أن يتم تعريف الماكرو
__cplusplus من قبل المترجم ويتوافق مع إصدار المعيار المدعوم:
C++ pre-C++98: #define __cplusplus 1 C++98: #define __cplusplus 199711L C++98 + TR1: #define __cplusplus 199711L
وفقًا لذلك ، يعد رمز تحديد مدى توفر الدعم تافهًا:
#if (__cplusplus >= 201103L)

في الواقع ، ليس كل شيء بهذه البساطة والآن تبدأ العكازات المثيرة للاهتمام مع أشعل النار.
أولاً ، لا يقوم جميع المترجمين ، أو بالأحرى لا شيء ، بتنفيذ المعيار التالي بشكل كامل وفوري. على سبيل المثال ، في Visual Studio 2013 ،
كان constexpr غائبًا
لفترة طويلة جدًا ، في حين زعم أنه يدعم C ++ 11 - مع التحذير بأن التنفيذ لم يكتمل. أي أن
auto - من فضلك ،
static_assert - بنفس السهولة (حتى من MS VS الأقدم) ، لكن
constexpr ليس كذلك . ثانيًا ، ليس كل المترجمين (وهذا أكثر إثارة للدهشة) يكشفون هذا التعريف ويحدثونه بشكل صحيح في الوقت المناسب. بشكل غير متوقع ، في نفس المحول البرمجي ،
لم يغير Visual Studio
إصدار تعريف __cplusplus من الإصدارات الأولى للمترجم ، على الرغم من أنه تم الإعلان عن الدعم الكامل لـ C ++ 11 (وهو أمر غير صحيح أيضًا ، حيث توجد أشعة منفصلة عن السخط - بمجرد أن تأتي المحادثة إلى الوظيفة المحددة لـ "الجديد "يقول 11 مطورًا قياسيًا على الفور أنه لا يوجد معالج مسبق C99 ، ولا توجد" ميزات "أخرى). ويتفاقم الوضع بحقيقة أنه من خلال المجمعين القياسيين يسمح لهم بتعيين هذا التعريف على أنه يختلف عن القيم المذكورة أعلاه ، إذا لم يلتزموا تمامًا بالمعايير المعلنة. سيكون من المنطقي أن نفترض ، على سبيل المثال ، مثل هذا التطور في تعريف ماكرو معين (مع إدخال وظائف جديدة ، زيادة العدد المخفي وراء هذا التعريف):
standart C++98: #define __cplusplus 199711L
ولكن في الوقت نفسه ، لا يتم "تهالك" أي من المجمعين الشعبيين الرئيسيين بهذه الميزة.
بسبب كل هذا (لست خائفاً من هذه الكلمة) ، الآن لكل مترجم غير قياسي عليك كتابة الشيكات الخاصة بك من أجل معرفة معيار C ++ وإلى أي مدى يدعم. الخبر السار هو أننا بحاجة إلى التعرف على عدد قليل فقط من وظائف المترجم للعمل بشكل صحيح. أولاً ، نضيف الآن فحص الإصدار لـ Visual Studio من خلال الماكرو
_MSC_VER ، الفريد لهذا المترجم. نظرًا لأنه في ترسانتي من المترجمين المدعومين ، هناك أيضًا C ++ Borland Builder 6.0 ، حيث كان المطورون حريصين بدورهم على الحفاظ على التوافق مع Visual Studio (بما في ذلك "الميزات" والأخطاء) ، ثم هناك فجأة هذا الماكرو هناك أيضًا. بالنسبة للمترجمات المتوافقة مع
clang ، هناك
__has_feature ماكرو غير قياسي
( feature_name
) ، والذي يسمح لك بمعرفة ما إذا كان المترجم يدعم هذه الوظيفة أو تلك. ونتيجة لذلك ، يتم تضخيم الرمز إلى:
#ifndef __has_feature #define __has_feature(x) 0
هل تريد الوصول إلى المزيد من المترجمين؟ نضيف فحوصات لـ Codegear C ++ Builder ، وهو وريث بورلاند (في أسوأ مظاهره ، ولكن أكثر حول ذلك لاحقًا):
#ifndef __has_feature #define __has_feature(x) 0
من الجدير بالذكر أيضًا أنه نظرًا لأن Visual Studio قد نفذ بالفعل دعم
nullptr من إصدار المحول البرمجي
_MSC_VER 1600 ، بالإضافة إلى الأنواع
المضمنة char16_t و
char32_t ، فنحن بحاجة إلى معالجة ذلك بشكل صحيح. تمت إضافة بعض عمليات التحقق الإضافية:
#ifndef __has_feature #define __has_feature(x) 0
في نفس الوقت ، سوف نتحقق من دعم C ++ 98 ، لأنه بالنسبة للمترجمين بدونها لن تكون هناك بعض ملفات رؤوس المكتبة القياسية ، ولا يمكننا التحقق من عدم وجودها باستخدام المترجم.
الخيار الكامل #ifndef __has_feature #define __has_feature(x) 0
والآن بدأت التكوينات الضخمة من التعزيز تظهر في ذاكرتي حيث كتب الكثير من المطورين المجتهدين كل وحدات الماكرو التي تعتمد على المترجم وصنعوا خريطة لما هو مدعوم وما لا يدعمه مترجم محدد لإصدار معين ، والذي أشعر به بعدم الارتياح ، لا أريد أبدًا النظر إليها أو لمسها بعد الآن. لكن الخبر السار هو أنه يمكنك التوقف عند هذا الحد. هذا على الأقل يكفي بالنسبة لي لدعم المترجمين الأكثر شيوعًا ، ولكن إذا وجدت عدم دقة أو تريد إضافة مترجم آخر ، فسأكون سعيدًا جدًا لقبول طلب السحب.
إنجاز رائع مقارنة بالدفع ، أعتقد أنه كان من الممكن الحفاظ على انتشار وحدات الماكرو المعتمدة على المترجم عبر الشفرة ، مما يجعل الشفرة أكثر نظافة وأسهل في الفهم ، وأيضًا لا تجمع عشرات ملفات التكوين لكل نظام تشغيل ولكل مترجم. سنتحدث عن مساوئ هذا النهج بعد ذلك بقليل.
في هذه المرحلة ، يمكننا بالفعل البدء في ربط الوظائف المفقودة من 11 معيارًا ، وأول شيء نقدمه هو
static_assert .
static_assert
نحدد بنية
StaticAssertion ، والتي ستأخذ قيمة منطقية كمعلمة قالب - سيكون هناك
شرطنا ، إذا لم يتم الوفاء به (التعبير
خطأ ) ، سيحدث خطأ في تجميع قالب غير متخصص. وهيكل وهمي آخر لاستقبال
sizeof ( StaticAssertion ) .
namespace stdex { namespace detail { template <bool> struct StaticAssertion; template <> struct StaticAssertion<true> { };
والمزيد من السحر الكلي
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message) #else
الاستخدام:
STATIC_ASSERT(sizeof(void*) == 4, non_x32_platform_is_unsupported);
هناك اختلاف مهم بين تنفيذي والتنفيذ القياسي وهو أنه لا يوجد تحميل زائد لهذه الكلمة الرئيسية دون إخبار المستخدم. هذا يرجع إلى حقيقة أنه في C ++ من المستحيل تعريف عدة تعريفات مع عدد مختلف من الوسيطات ولكن اسم واحد ، والتنفيذ بدون رسالة أقل فائدة بكثير من الخيار المحدد. تؤدي هذه الميزة إلى حقيقة أن STATIC_ASSERT في جوهر عملي هو الإصدار الذي تمت إضافته بالفعل في C ++ 11.
دعونا نلقي نظرة على ما حدث. نتيجة لفحص إصدارات
__cplusplus ووحدات ماكرو المترجم غير القياسية ، لدينا معلومات كافية حول دعم C ++ 11 (وبالتالي
static_assert ) ، معبر عنها بواسطة _STDEX_NATIVE_CPP11_SUPPORT. لذلك ، إذا تم تعريف هذا الماكرو ، يمكننا ببساطة استخدام
static_assert القياسي:
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message)
يرجى ملاحظة أن المعلمة الثانية للماكرو STATIC_ASSERT ليست سلسلة حرفية على الإطلاق ، وبالتالي باستخدام معامل المعالج # سنقوم بتحويل معلمة الرسالة إلى سلسلة لإرسالها إلى static_assert القياسي.
إذا لم يكن لدينا دعم من المترجم ، فإننا ننتقل إلى تنفيذنا. بادئ ذي بدء ، سوف نعلن عن وحدات ماكرو مساعدة لسلاسل "اللصق" (مشغل ما قبل المعالج
## هو المسؤول فقط عن ذلك).
#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2) #define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2) #define CONCATENATE2(arg1, arg2) arg1##arg2
لم أستخدم على وجه التحديد #define CONCATENATE ( arg1 ، arg2 ) arg1 ## arg2 لكي أتمكن من تمرير نتيجة ماكرو CONCATENATE نفسه كوسيطة إلى arg1 و arg2 .
بعد ذلك ، نعلن عن بنية تحمل الاسم الجميل __static_assertion_at_line_ {line number} (يتم تعريف الماكرو
__LINE__ أيضًا بالمعيار ويجب توسيعه إلى رقم السطر الذي تم تسميته عليه) ، وداخل هذا الهيكل نضيف حقلًا من نوع
StaticAssertion باسم STATIC_ASSERTION_FAILED_AT_LINE_ {رقم السطر}} رسائل خطأ من الماكرو الاستدعاء}.
#define STATIC_ASSERT(expression, message)\ struct CONCATENATE(__static_assertion_at_line_, __LINE__)\ {\ stdex::detail::StaticAssertion<static_cast<bool>((expression))> CONCATENATE(CONCATENATE(CONCATENATE(STATIC_ASSERTION_FAILED_AT_LINE_, __LINE__), _WITH__), message);\ };\ typedef stdex::detail::StaticAssertionTest<sizeof(CONCATENATE(__static_assertion_at_line_, __LINE__))> CONCATENATE(__static_assertion_test_at_line_, __LINE__)
باستخدام معلمة القالب في
StaticAssertion ، نقوم بتمرير تعبير محدد في
STATIC_ASSERT ، مما يؤدي إلى
خدعة . أخيرًا ، من أجل تجنب إنشاء المتغيرات المحلية والتحقق من حالة الحمل
الزائد ، يتم الإعلان عن اسم مستعار للنوع
StaticAssertionTest <sizeof ({name of the هيكل المعلن أعلاه}) بالاسم __static_assertion_test_at_line_ {رقم السطر}.
كل الجمال مع التسمية ضروري فقط لتوضيح من خطأ تجميع أن هذه نتيجة مؤكدة ، وليس مجرد خطأ ، ولكن أيضًا لعرض رسالة خطأ تم تعيينها لهذا التأكيد. إن خدعة
sizeof ضرورية لإجبار المترجم على إنشاء فئة قالب
StaticAssertion ، التي تقع داخل الهيكل المعلن للتو ، وبالتالي تحقق من الحالة التي تم تمريرها للتأكيد.
نتائج STATIC_ASSERTدول مجلس التعاون الخليجي:
30: 103: خطأ: يحتوي الحقل 'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' على نوع غير كامل 'stdex :: detail :: StaticAssertion <false>'
25:36: ملاحظة: في تعريف الماكرو "CONCATENATE2"
23:36: ملاحظة: في توسيع الماكرو "CONCATENATE1"
30:67: ملاحظة: في توسيع الماكرو "CONCATENATE"
24:36: ملاحظة: في توسيع الماكرو "CONCATENATE2"
23:36: ملاحظة: في توسيع الماكرو "CONCATENATE1"
30:79: ملاحظة: في توسيع الماكرو "CONCATENATE"
24:36: ملاحظة: في توسيع الماكرو "CONCATENATE2"
23:36: ملاحظة: في توسيع الماكرو "CONCATENATE1"
30:91: ملاحظة: في توسيع الماكرو "CONCATENATE"
36: 3: ملاحظة: في توسيع الماكرو "STATIC_ASSERT"
Borland C ++ Builder:
[خطأ C ++] stdex_test.cpp (36): E2450 بنية غير محددة 'stdex :: detail :: StaticAssertion <0>'
[خطأ C ++] stdex_test.cpp (36): E2449 حجم 'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' غير معروف أو صفر
[خطأ C ++] stdex_test.cpp (36): E2450 بنية غير محددة 'stdex :: detail :: StaticAssertion <0>'
Visual Studio:
خطأ c2079
"الحيلة" الثانية التي أردت
امتلاكها ، في حين أنه
مفقود من المعيار هو العد - حساب عدد العناصر في المصفوفة. Sishers مغرمون جدًا بالإعلان عن هذا الماكرو من خلال sizeof (arr) / sizeof (arr [0]) ، ولكننا سنذهب أبعد من ذلك.
العد
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #include <cstddef> namespace stdex { namespace detail { template <class T, std::size_t N> constexpr std::size_t _my_countof(T const (&)[N]) noexcept { return N; } } // namespace detail } #define countof(arr) stdex::detail::_my_countof(arr) #else //no C++11 support #ifdef _STDEX_NATIVE_MICROSOFT_COMPILER_EXTENSIONS_SUPPORT // Visual C++ fallback #include <stdlib.h> #define countof(arr) _countof(arr) #elif defined(_STDEX_NATIVE_CPP_98_SUPPORT)// C++ 98 trick #include <cstddef> template <typename T, std::size_t N> char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N]; #define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x)) #else #define countof(arr) sizeof(arr) / sizeof(arr[0]) #endif
بالنسبة
للمجمعين الذين يدعمون
constexpr ، سنعلن عن إصدار constexpr من هذا القالب (وهو غير ضروري على الإطلاق ، بالنسبة لجميع المعايير ،
يكفي التنفيذ من خلال نموذج
COUNTOF_REQUIRES_ARRAY_ARGUMENT ) ، أما بالنسبة للبقية
فنقدم الإصدار من خلال وظيفة القالب
COUNTOF_REQUIRES_ARRAY_ARGUMENT . يتميز Visual Studio هنا مرة أخرى بوجود تطبيق خاص به لـ
_countof في ملف رأس
stdlib.h .
تبدو الدالة
COUNTOF_REQUIRES_ARRAY_ARGUMENT مرعبة
ومعرفة أن ما تفعله أمر صعب للغاية. إذا نظرت عن كثب ، يمكنك أن تفهم أنه يأخذ مجموعة من العناصر من نوع القالب
T والحجم
N كوسيطة فقط - وبالتالي ، في حالة نقل أنواع أخرى من العناصر (وليس المصفوفات) ، نحصل على خطأ في الترجمة ، مما يرضي بلا شك. بإلقاء نظرة فاحصة ، يمكنك أن تكتشف (بصعوبة) أنها تُرجع مجموعة من عناصر
الحرف ذات الحجم
N. السؤال هو لماذا نحتاج كل هذا؟ هذا هو المكان الذي يتم فيه تشغيل عامل
sizeof وقدرته الفريدة على العمل في وقت الترجمة. يحدد
sizeof المكالمة
( COUNTOF_REQUIRES_ARRAY_ARGUMENT ) حجم صفيف عناصر
الأحرف التي أرجعتها الدالة ، وبما أن
sizeof القياسي
(char) == 1 ، هذا هو عدد عناصر
N في الصفيف الأصلي. أنيقة وجميلة وخالية تمامًا.
الى الابد
هناك ماكرو مساعد صغير آخر أستخدمه حيثما دعت الحاجة
إلى حلقة لا نهائية وهو إلى
الأبد . يتم تعريفها على النحو التالي:
#if !defined(forever) #define forever for(;;) #else #define STRINGIZE_HELPER(x) #x #define STRINGIZE(x) STRINGIZE_HELPER(x) #define WARNING(desc) message(__FILE__ "(" STRINGIZE(__LINE__) ") : warning: " desc) #pragma WARNING("stdex library - macro 'forever' was previously defined by user; ignoring stdex macro definition") #undef STRINGIZE_HELPER #undef STRINGIZE #undef WARNING #endif
مثال على بناء الجملة لتعريف حلقة لانهائية صريحة:
unsigned int i = 0; forever { ++i; }
يتم استخدام هذا الماكرو فقط لتعريف حلقة لا نهائية بشكل صريح ويتم تضمينها في المكتبة فقط لأسباب "إضافة السكر النحوي". في المستقبل ، أقترح استبداله اختياريًا من خلال تحديد الماكرو الإضافي
FOREVER . ما هو ملحوظ في مقتطف الشفرة أعلاه من المكتبة هو نفس ماكرو
التحذير الذي يولد رسالة تحذير في جميع المجمعين إذا كان الماكرو
إلى الأبد قد حدد بالفعل من قبل المستخدم. يستخدم الماكرو
__LINE__ القياسي
المألوف والماكرو __FILE__ القياسي ، والذي يتم تحويله إلى سلسلة باسم ملف المصدر الحالي.
stdex_assert
لتنفيذ
التأكيد في وقت التشغيل ،
يتم تقديم الماكرو
stdex_assert على النحو التالي:
#if defined(assert) #ifndef NDEBUG #include <iostream> #define stdex_assert(condition, message) \ do { \ if (! (condition)) { \ std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \ << " line " << __LINE__ << ": " << message << std::endl; \ std::terminate(); \ } \ } while (false) #else #define stdex_assert(condition, message) ((void)0) #endif #endif
لن أقول إنني فخور جدًا بهذا التنفيذ (سيتم تغييره في المستقبل) ، ولكن تم استخدام تقنية مثيرة للاهتمام هنا أود أن ألفت الانتباه إليها. من أجل إخفاء الشيكات من نطاق رمز التطبيق ، يتم استخدام البناء
{} أثناء (خطأ) ، والذي سيتم تنفيذه ، وهو أمر واضح مرة واحدة وفي نفس الوقت لن يقدم رمز "الخدمة" في رمز التطبيق العام. هذه التقنية مفيدة جدًا ويتم استخدامها في عدة أماكن أخرى في المكتبة.
خلاف ذلك ، فإن التنفيذ يشبه إلى حد كبير
التأكيد القياسي - مع ماكرو
NDEBUG معين ، والذي يقوم
المترجمون بتعيينه عادةً في إصدارات الإصدار ، يؤكد عدم القيام بأي شيء ، وإلا فإنه يقاطع تنفيذ البرنامج مع إخراج الرسالة إلى تدفق الخطأ القياسي إذا لم يتم استيفاء شرط التأكيد.
لا يوجد استثناء
بالنسبة للوظائف التي لا ترمي استثناءات ، تم إدخال الكلمة الرئيسية
noexcept في المعيار الجديد. كما أنه من السهل جدًا تنفيذه من خلال الماكرو:
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define stdex_noexcept noexcept #else #define stdex_noexcept throw() #endif
ومع ذلك ، من الضروري أن نفهم أنه وفقًا
للمعيار القياسي ، يمكن أن تأخذ القيمة
المنطقية ، ويمكن أيضًا
استخدامها لتحديد وقت الترجمة أن التعبير الذي تم تمريره إليها لا يؤدي إلى استثناء. لا يمكن تنفيذ هذه الوظيفة بدون دعم المترجم ، وبالتالي لا يوجد سوى
stdex_noexcept في "المكتبة".
نهاية الفصل الثاني. سيتحدث
الفصل الثالث عن تعقيدات تنفيذ nullptr ، وسبب اختلافه عن المترجمين المختلفين ، وكذلك تطوير type_traits ، وما هي الأخطاء الأخرى في المترجمات التي صادفتها أثناء تطويره.
شكرا لكم على اهتمامكم.