كيف كتبت مكتبة C ++ 11 القياسية أو لماذا يكون التعزيز مخيفًا جدًا. الفصل 3

نواصل المغامرة.

ملخص الأجزاء السابقة


نظرًا للقيود المفروضة على القدرة على استخدام برامج التحويل البرمجي لـ C ++ 11 ، وبسبب عدم وجود بدائل ، أراد التعزيز كتابة تطبيقه الخاص لمكتبة C ++ 11 القياسية أعلى مكتبة C ++ 98 / C ++ 03 المزودة مع برنامج التحويل البرمجي.

تم تنفيذ Static_assert ، noexcept ، countof ، وأيضًا بعد النظر في جميع التعريفات غير القياسية وميزات المترجم ، ظهرت معلومات حول الوظيفة التي يدعمها المترجم الحالي. يكمل هذا وصف core.h ، ولكنه لن يكتمل بدون nullptr .

رابط إلى 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 ما هو مطلوب أيضًا لمكتبة النماذج
الفصل الخامس
...

الفصل 3. إيجاد تنفيذ nullptr الكمال


بعد الملحمة الكاملة مع وحدات ماكرو المترجم غير القياسية والاكتشافات "الرائعة" التي قدموها ، تمكنت أخيرًا من إضافة nullptr وهو نوع من تدفئة روحي. أخيرًا ، يمكنك التخلص من كل هذه المقارنات بـ 0 أو حتى NULL .

الصورة يقوم معظم المبرمجين بتطبيق nullptr كـ
#define nullptr 0 

وهذا يمكن أن يكون قد أنهى هذا الفصل. إذا كنت تريد نفسك nullptr ، فاستبدل 0 بمثل هذا التعريف ، لأنه في الأساس هذا كل ما هو مطلوب للتشغيل الصحيح.

لا تنس أن تكتب حقًا شيكًا ، وإلا فسيتم العثور على شخص آخر بهذا التعريف:

 #ifndef nullptr #define nullptr 0 #else #error "nullptr defined already" #endif 

سينتج عن توجيه # المعالج ما قبل الخطأ خطأ في النص الذي يمكن قراءته بواسطة الإنسان عند الترجمة ، ونعم ، هذا توجيه قياسي ، نادرًا ما يتم استخدامه ، ولكن يمكن العثور عليه.

ولكن في مثل هذا التنفيذ ، نفتقد إحدى النقاط المهمة الموضحة في المعيار ، وهي std :: nullptr_t - نوع منفصل ، ومثال ثابت هو nullptr . كما حاول مطورو الكروم أيضًا مرة واحدة حل هذه المشكلة (الآن هناك مترجم جديد و nullptr العادي) يعرّفها على أنها فئة يمكن تحويلها إلى مؤشر إلى أي نوع. نظرًا لأنه ، وفقًا للمعيار القياسي ، يجب أن يكون حجم nullptr مساويًا لحجم المؤشر المراد إبطاله (ويجب أن يحتوي void * أيضًا على أي مؤشر ، باستثناء المؤشرات إلى عضو في الفصل الدراسي) ، فإننا "نقوم بتوحيد" هذا التنفيذ بإضافة مؤشر فارغ غير مستخدم:

 class nullptr_t_as_class_impl { public: nullptr_t_as_class_impl() { } nullptr_t_as_class_impl(int) { } // Make nullptr convertible to any pointer type. template<typename T> operator T*() const { return 0; } // Make nullptr convertible to any member pointer type. template<typename C, typename T> operator TC::*() { return 0; } bool operator==(nullptr_t_as_class_impl) const { return true; } bool operator!=(nullptr_t_as_class_impl) const { return false; } private: // Do not allow taking the address of nullptr. void operator&(); void *_padding; }; typedef nullptr_t_as_class_impl nullptr_t; #define nullptr nullptr_t(0) 

إن تحويل هذه الفئة إلى أي مؤشر يرجع إلى عامل القالب من النوع ، والذي يسمى إذا تم مقارنة شيء مع nullptr . أي أن التعبير char * my_pointer؛ إذا كان سيتم بالفعل تحويل (my_pointer == nullptr) إلى if (my_pointer == nullptr.operator char * ()) ، الذي يقارن المؤشر بـ 0. يلزم استخدام عامل النوع الثاني لتحويل nullptr إلى مؤشرات لأعضاء الفئة. وهنا Borland C ++ Builder 6.0 "ميز نفسه" ، الذي قرر بشكل غير متوقع أن هاتين المشغلين متطابقان ويمكنهما بسهولة مقارنة المؤشرات لعضو الفئة والمؤشرات العادية مع بعضهما البعض ، لذلك هناك عدم اليقين في كل مرة تتم مقارنة مثل هذه nullptr مع المؤشر (هذا خطأ ، وربما ليس فقط مع هذا المترجم). نكتب تنفيذ منفصل لهذه الحالة:

 class nullptr_t_as_class_impl1 { public: nullptr_t_as_class_impl1() { } nullptr_t_as_class_impl1(int) { } // Make nullptr convertible to any pointer type. template<typename T> operator T*() const { return 0; } bool operator==(nullptr_t_as_class_impl1) const { return true; } bool operator!=(nullptr_t_as_class_impl1) const { return false; } private: // Do not allow taking the address of nullptr. void operator&(); void *_padding; }; typedef nullptr_t_as_class_impl1 nullptr_t; #define nullptr nullptr_t(0) 

مزايا طريقة عرض nullptr هي وجود نوع منفصل لـ std :: nullptr_t . مساوئ؟ يتم فقدان ثابت nullptr أثناء التجميع والمقارنة من خلال عامل التشغيل الثلاثي ، ولا يمكن للمترجم حلها.

 unsigned* case5 = argc > 2 ? (unsigned*)0 : nullptr; //  ,     ':'    STATIC_ASSERT(nullptr == nullptr && !(nullptr != nullptr), nullptr_should_be_equal_itself); //  , nullptr      

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

 #ifdef NULL #define STDEX_NULL NULL #else #define STDEX_NULL 0 #endif namespace ptrdiff_detail { using namespace std; } template<bool> struct nullptr_t_as_ulong_type { typedef unsigned long type; }; template<> struct nullptr_t_as_ulong_type<false> { typedef unsigned long type; }; template<bool> struct nullptr_t_as_ushort_type { typedef unsigned short type; }; template<> struct nullptr_t_as_ushort_type<false> { typedef nullptr_t_as_long_type<sizeof(unsigned long) == sizeof(void*)>::type type; }; template<bool> struct nullptr_t_as_uint_type { typedef unsigned int type; }; template<> struct nullptr_t_as_uint_type<false> { typedef nullptr_t_as_short_type<sizeof(unsigned short) == sizeof(void*)>::type type; }; typedef nullptr_t_as_uint_type<sizeof(unsigned int) == sizeof(void*)>::type nullptr_t_as_uint; enum nullptr_t_as_enum { _nullptr_val = ptrdiff_detail::ptrdiff_t(STDEX_NULL), _max_nullptr = nullptr_t_as_uint(1) << (CHAR_BIT * sizeof(void*) - 1) }; typedef nullptr_t_as_enum nullptr_t; #define nullptr nullptr_t(STDEX_NULL) 

كما ترى هنا رمز أكثر بقليل من مجرد الإعلان عن التعداد nullptr_t مع العضو nullptr = 0 . أولاً ، قد لا يكون هناك تعريفات فارغة . يجب أن يتم تعريفه في قائمة صلبة إلى حد ما من الرؤوس القياسية ، ولكن كما أظهرت الممارسة ، فمن الأفضل تشغيله بأمان والتحقق من هذا الماكرو. ثانيًا ، تمثيل التعداد في C ++ وفقًا لمعيار التنفيذ المحدد ، أي يمكن تمثيل نوع التعداد بأي أنواع صحيحة (بشرط أن هذه الأنواع لا يمكن أن تكون أكثر من عدد صحيح ، بشرط أن تتناسب قيم التعداد فيه). على سبيل المثال ، إذا أعلنت عن اختبار التعداد {_1 ، _2} ، يمكن للمترجم تمثيله بسهولة على أنه قصير ، وبعد ذلك من الممكن أن يكون sizeof ( test ) ! = Sizeof (void *) . لكي يتوافق تنفيذ nullptr مع المعيار ، عليك التأكد من أن حجم النوع الذي يختاره المحول البرمجي لـ nullptr_t_as_enum يتطابق مع حجم المؤشر ، أي يساوي في الأساس حجم (باطل *) . للقيام بذلك ، باستخدام قوالب nullptr_t_as ... ، حدد نوع عدد صحيح يساوي حجم المؤشر ، ثم قم بتعيين الحد الأقصى لقيمة العنصر في تعدادنا إلى القيمة القصوى لهذا النوع الصحيح.
أريد الانتباه إلى الماكرو CHAR_BIT المحدد في رأس المناخات القياسية. يتم تعيين هذا الماكرو على عدد البتات في حرف واحد ، أي عدد البتات لكل بايت على النظام الأساسي الحالي. تعريف قياسي مفيد يتجنبه المطورون دون تحفظ من خلال لصق الثماني في كل مكان ، على الرغم من عدم وجود 8 بت على الإطلاق في بعض الأماكن في بايت واحد .

وميزة أخرى هي تعيين NULL كقيمة عنصر التعداد . يعطي بعض المترجمين تحذيرًا (ويمكن فهم مخاوفهم) حول حقيقة أن NULL يتم تعيينها إلى "غير مفهرس". نأخذ مساحة الاسم القياسية إلى ptrdiff_detail المحلي الخاص بنا ، حتى لا نشوش على بقية مساحة الاسم ، ومن ثم ، لتهدئة المترجم ، نقوم بتحويل NULL بشكل صريح إلى std :: ptrdiff_t - نوع آخر غير مستخدم بشكل أو بآخر في C ++ ، والذي يعمل على تمثيل نتيجة العمليات الحسابية (طرح) بمؤشرات وعادة ما يكون اسمًا مستعارًا من النوع std :: size_t ( std :: intptr_t في C ++ 11).

SFINAE


هنا ، لأول مرة في قصتي ، نواجه مثل هذه الظاهرة في C ++ لأن فشل الاستبدال ليس خطأ (SFINAE) . باختصار ، جوهره هو أنه عندما "يمر" المترجم بالوظيفة الزائدة للوظيفة المناسبة لمكالمة معينة ، يجب عليه التحقق منها جميعًا ، وعدم التوقف بعد الفشل الأول أو بعد أول حمل زائد مناسب. من هنا تأتي رسالته حول الغموض ، عندما يكون هناك حمولة زائدة للدالة المطلوبة متطابقة من وجهة نظر المترجم ، بالإضافة إلى قدرة المترجم على تحديد أكثر وظيفة تحميل أكثر دقة لمكالمة محددة مع معلمات محددة. تتيح لك ميزة المترجم هذه القيام بنصيب الأسد من كل قالب "السحر" (بالمناسبة hi std :: enable_if ) ، وهي أيضًا أساس التعزيز ومكتبتي .

نظرًا لأننا ، نتيجة لذلك ، لدينا العديد من تطبيقات nullptr ، فإننا نستخدم SFINAE "حدد" الأفضل في مرحلة التجميع. نعلن عن نوعي "نعم" و "لا" للتحقق من حجم وظائف المسبار الموضحة أدناه.

 namespace nullptr_detail { typedef char _yes_type; struct _no_type { char padding[8]; }; struct dummy_class {}; _yes_type _is_convertable_to_void_ptr_tester(void*); _no_type _is_convertable_to_void_ptr_tester(...); typedef void(nullptr_detail::dummy_class::*dummy_class_f)(int); typedef int (nullptr_detail::dummy_class::*dummy_class_f_const)(double&) const; _yes_type _is_convertable_to_member_function_ptr_tester(dummy_class_f); _no_type _is_convertable_to_member_function_ptr_tester(...); _yes_type _is_convertable_to_const_member_function_ptr_tester(dummy_class_f_const); _no_type _is_convertable_to_const_member_function_ptr_tester(...); template<class _Tp> _yes_type _is_convertable_to_ptr_tester(_Tp*); template<class> _no_type _is_convertable_to_ptr_tester(...); } 

سنستخدم هنا نفس المبدأ كما في الفصل الثاني مع countof وتعريفه من خلال حجم القيمة المرجعة (مجموعة العناصر) لوظيفة القالب COUNTOF_REQUIRES_ARRAY_ARGUMENT .

 template<class T> struct _is_convertable_to_void_ptr_impl { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_void_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); }; 

ما الذي يحدث هنا؟ أولاً ، المترجم " يكرر " الأحمال الزائدة للدالة _is_convertable_to_void_ptr_tester مع وسيطة من النوع T وقيمة NULL (القيمة لا تلعب دورًا ، فقط NULL يجب أن تكون من النوع- T ). هناك نوعان فقط من التحميل الزائد - مع نوع الفراغ * وقائمة المتغيرات (...) . باستبدال وسيطة في كل من هذه الأحمال الزائدة ، سيحدد المترجم الأول إذا تم تحويل النوع إلى مؤشر لإبطاله ، والثاني إذا تعذر تنفيذ الإلقاء. مع التحميل الزائد الذي حدده المحول البرمجي ، نستخدم sizeof لتحديد حجم القيمة التي أرجعتها الدالة ، وبما أنها مضمونة لتكون مختلفة ( sizeof ( _no_type ) == 8 ، sizeof ( _yes_type ) == 1 ) ، يمكننا تحديد حجم الحمل الزائد الذي التقطه المحول البرمجي وبالتالي يحول سواء كان نوعنا باطل * أم لا.

سنطبق قالب البرمجة نفسه بشكل أكبر لتحديد ما إذا كان كائن من النوع الذي اخترناه لتمثيل nullptr_t يتم تحويله إلى أي مؤشر (بشكل أساسي (T) ( STDEX_NULL ) هو التعريف المستقبلي لـ nullptr ).

 template<class T> struct _is_convertable_to_member_function_ptr_impl { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_member_function_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)) && (sizeof(nullptr_detail::_is_convertable_to_const_member_function_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); }; template<class NullPtrType, class T> struct _is_convertable_to_any_ptr_impl_helper { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_ptr_tester<T>((NullPtrType) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); }; template<class T> struct _is_convertable_to_any_ptr_impl { static const bool value = _is_convertable_to_any_ptr_impl_helper<T, int>::value && _is_convertable_to_any_ptr_impl_helper<T, float>::value && _is_convertable_to_any_ptr_impl_helper<T, bool>::value && _is_convertable_to_any_ptr_impl_helper<T, const bool>::value && _is_convertable_to_any_ptr_impl_helper<T, volatile float>::value && _is_convertable_to_any_ptr_impl_helper<T, volatile const double>::value && _is_convertable_to_any_ptr_impl_helper<T, nullptr_detail::dummy_class>::value; }; template<class T> struct _is_convertable_to_ptr_impl { static const bool value = ( _is_convertable_to_void_ptr_impl<T>::value == bool(true) && _is_convertable_to_any_ptr_impl<T>::value == bool(true) && _is_convertable_to_member_function_ptr_impl<T>::value == bool(true) ); }; 

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

كما هو مذكور أعلاه ، لا يميز بعض جامعي (* khe-khe * ... Borland Builder 6.0 ... * khe *) بين المؤشرات إلى نوع وعضو في فئة ، لذلك سنكتب فحصًا مساعدًا آخر لهذه الحالة حتى نتمكن من تحديد التطبيق المطلوب لـ nullptr_t من خلال الفصل إذا لزم الأمر.

 struct _member_ptr_is_same_as_ptr { struct test {}; typedef void(test::*member_ptr_type)(void); static const bool value = _is_convertable_to_void_ptr_impl<member_ptr_type>::value; }; template<bool> struct _nullptr_t_as_class_chooser { typedef nullptr_detail::nullptr_t_as_class_impl type; }; template<> struct _nullptr_t_as_class_chooser<false> { typedef nullptr_detail::nullptr_t_as_class_impl1 type; }; 

ثم يبقى فقط للتحقق من تطبيقات مختلفة من nullptr_t واختيار المترجم المناسب للمترجم.

اختيار تنفيذ nullptr_t
 template<bool> struct _nullptr_choose_as_int { typedef nullptr_detail::nullptr_t_as_int type; }; template<bool> struct _nullptr_choose_as_enum { typedef nullptr_detail::nullptr_t_as_enum type; }; template<bool> struct _nullptr_choose_as_class { typedef _nullptr_t_as_class_chooser<_member_ptr_is_same_as_ptr::value>::type type; }; template<> struct _nullptr_choose_as_int<false> { typedef nullptr_detail::nullptr_t_as_void type; }; template<> struct _nullptr_choose_as_enum<false> { struct as_int { typedef nullptr_detail::nullptr_t_as_int nullptr_t_as_int; static const bool _is_convertable_to_ptr = _is_convertable_to_ptr_impl<nullptr_t_as_int>::value; static const bool _equal_void_ptr = _is_equal_size_to_void_ptr<nullptr_t_as_int>::value; }; typedef _nullptr_choose_as_int<as_int::_is_convertable_to_ptr == bool(true) && as_int::_equal_void_ptr == bool(true)>::type type; }; template<> struct _nullptr_choose_as_class<false> { struct as_enum { typedef nullptr_detail::nullptr_t_as_enum nullptr_t_as_enum; static const bool _is_convertable_to_ptr = _is_convertable_to_ptr_impl<nullptr_t_as_enum>::value; static const bool _equal_void_ptr = _is_equal_size_to_void_ptr<nullptr_t_as_enum>::value; static const bool _can_be_ct_constant = true;//_nullptr_can_be_ct_constant_impl<nullptr_t_as_enum>::value; }; typedef _nullptr_choose_as_enum<as_enum::_is_convertable_to_ptr == bool(true) && as_enum::_equal_void_ptr == bool(true) && as_enum::_can_be_ct_constant == bool(true)>::type type; }; struct _nullptr_chooser { struct as_class { typedef _nullptr_t_as_class_chooser<_member_ptr_is_same_as_ptr::value>::type nullptr_t_as_class; static const bool _equal_void_ptr = _is_equal_size_to_void_ptr<nullptr_t_as_class>::value; static const bool _can_be_ct_constant = _nullptr_can_be_ct_constant_impl<nullptr_t_as_class>::value; }; typedef _nullptr_choose_as_class<as_class::_equal_void_ptr == bool(true) && as_class::_can_be_ct_constant == bool(true)>::type type; }; 


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

في هذه المرحلة ، تم الكشف عن معظم قوة SFINAE بالاشتراك مع قوالب C ++ ، والتي يمكن من خلالها اختيار التنفيذ الضروري دون اللجوء إلى وحدات الماكرو المعتمدة على المترجم ، وفي الواقع إلى وحدات الماكرو (على عكس التعزيز حيث سيكون كل هذا مكتظًا باستخدام #ifdef #else # الشيكات ).

يبقى فقط لتعريف نوع الاسم المستعار لـ nullptr_t في مساحة الاسم stdex وتعريف لـ nullptr (من أجل الامتثال لمتطلبات قياسية أخرى أنه لا يمكن أخذ عنوان nullptr ، وكذلك لاستخدام nullptr باعتباره ثابت وقت ترجمة).

 namespace stdex { typedef detail::_nullptr_chooser::type nullptr_t; } #define nullptr (stdex::nullptr_t)(STDEX_NULL) 

نهاية الفصل الثالث. في الفصل الرابع ، وصلت أخيرًا إلى type_traits وما هي الأخطاء الأخرى في المترجمات التي صادفتها أثناء التطوير.

شكرا لكم على اهتمامكم.

Source: https://habr.com/ru/post/ar417295/


All Articles