
ملخص الأجزاء السابقة
نظرًا للقيود المفروضة على القدرة على استخدام برامج التحويل البرمجي لـ C ++ 11 ، وبسبب عدم وجود بدائل ، أراد التعزيز كتابة تطبيقه الخاص لمكتبة C ++ 11 القياسية أعلى مكتبة C ++ 98 / C ++ 03 المزودة مع برنامج التحويل البرمجي.
تم تنفيذ
Static_assert ،
noexcept ،
countof ، وأيضًا بعد النظر في جميع
التعريفات غير القياسية وميزات المترجم ، ظهرت معلومات حول الوظيفة التي يدعمها المترجم الحالي.
يتم تضمين تطبيقه الخاص لـ
nullptr ، والذي يتم اختياره في مرحلة التجميع.
لقد حان الوقت
لنوع type_traits وكل هذا "سحر القالب الخاص". في الجزء الأول ، درسنا تنفيذ أبسط النماذج للمكتبة القياسية ، ولكننا سنتعمق أكثر في النماذج.
رابط إلى GitHub بالنتيجة لهذا اليوم للصبر وغير القراء:
نرحب بالالتزامات والنقد البناء
استمرار الانغماس في عالم "قالب السحر" C ++.
جدول المحتويات
مقدمةالفصل 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 ما هو مطلوب أيضًا لمكتبة النماذجالفصل الخامس
...
الفصل 4. قالب "سحر" C ++. استمرار
4.2 حول عدد الأخطاء المعجزة التي يجمعها السجل
في
الجزء الأول من هذا الفصل ، تم تقديم قوالب
type_traits الأساسية ، ولكن عددًا أقل منها مفقود للمجموعة الكاملة.
على سبيل المثال ، كانت القوالب
is_ كاملal و
is_floating_point ضرورية ببساطة ، والتي تم تعريفها بشكل تافه للغاية - من خلال
تخصيص القالب لكل نوع مضمن. طرح السؤال هنا فقط مع الأنواع "الكبيرة"
الطويلة الطويلة . والحقيقة هي أن هذا النوع مدمج يظهر في معيار لغة C ++ فقط من الإصدار 11. ومن المنطقي أن نفترض أن الأمر كله يرجع إلى التحقق من إصدار معيار C ++ (الذي
يصعب تحديده على أي حال ) ، ولكنه لم يكن موجودًا.

لأنه ، منذ عام 1999 ، يوجد معيار لغة C99 C حيث توجد بالفعل أنواع
طويلة طويلة int وغير
موقعة طويلة طويلة (منذ 1999!) ، ومنذ أن سعت لغة C ++ إلى الحفاظ على التوافق مع C النقي ، العديد من المترجمات (التي عادة ما تكون مختلطة C / C ++) فقط إضافتها كنوع أساسي حتى قبل إصدار معيار C ++ 03. بمعنى ، كان الوضع هو أن النوع المدمج هو في الواقع (من C) ، ولكن لم يتم وصفه في معيار C ++ ولا يجب أن يكون موجودًا. وهذا يضيف المزيد من الارتباك إلى تنفيذ المكتبة القياسية. لكن دعنا ننظر إلى الرمز:
namespace detail { template <class> struct _is_floating_point : public false_type {}; template<> struct _is_floating_point<float> : public true_type {}; template<> struct _is_floating_point<double> : public true_type {}; template<> struct _is_floating_point<long double> : public true_type {}; } template <class _Tp> struct is_floating_point : public detail::_is_floating_point<typename remove_cv<_Tp>::type> { };
كل شيء واضح مع الرمز أعلاه - نحن متخصصون في القالب لأنواع الفاصلة العائمة الضرورية ، وبعد "مسح" معدِّلات النوع ، نقول "نعم" أو "لا" للنوع الذي تم تمريره إلينا. التالي في السطر أنواع صحيحة:
namespace detail { template <class> struct _is_integral_impl : public false_type {}; template<> struct _is_integral_impl<bool> : public true_type {}; template<> struct _is_integral_impl<char> : public true_type {}; template<> struct _is_integral_impl<wchar_t> : public true_type {}; template<> struct _is_integral_impl<unsigned char> : public true_type {}; template<> struct _is_integral_impl<unsigned short int> : public true_type {}; template<> struct _is_integral_impl<unsigned int> : public true_type {}; template<> struct _is_integral_impl<unsigned long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<unsigned long long int> : public true_type {}; #endif template<> struct _is_integral_impl<signed char> : public true_type {}; template<> struct _is_integral_impl<short int> : public true_type {}; template<> struct _is_integral_impl<int> : public true_type {}; template<> struct _is_integral_impl<long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<long long int> : public true_type {}; #endif template <class _Tp> struct _is_integral : public _is_integral_impl<_Tp> {}; template<> struct _is_integral<char16_t> : public true_type {}; template<> struct _is_integral<char32_t> : public true_type {}; template<> struct _is_integral<int64_t> : public true_type {}; template<> struct _is_integral<uint64_t> : public true_type {}; } template <class _Tp> struct is_integral : public detail::_is_integral<typename remove_cv<_Tp>::type> { };
هنا تحتاج للتوقف قليلاً والتفكير. لأنواع الأعداد الصحيحة "القديمة" مثل
int ،
bool ، إلخ. نقوم بنفس التخصصات كما هو الحال مع
is_floating_point . بالنسبة للأنواع "الجديدة" التي تكون
طويلة الأمد مع نظيرتها غير الموقعة ، فإننا لا نحدد الزائد إلا إذا كان هناك
تعريف LLONG_MAX ، والذي تم تعريفه في C ++ 11 (كأول معيار C ++ متوافق مع C99) ، ويجب تعريفه في ملف رأس
المناخات كحد أقصى عدد كبير يناسب كائنًا من النوع
الطويل الطويل .
يحتوي Climits أيضًا على عدد قليل من تعريفات الماكرو (لأصغر عدد ممكن ومعادلات غير موقعة) ، لكنني قررت استخدام هذا الماكرو ، وهو أمر غير مهم. الشيء المهم هو أنه ، على عكس التعزيز ، في هذا التطبيق لن يتم تعريف الأنواع "الكبيرة" من C على أنها ثوابت صحيحة ، على الرغم من أنها (ربما) موجودة في المترجم. ما هو مهم أيضًا هو النوعين
char16_t و
char32_t ، اللذان تم
تقديمهما أيضًا في C ++ 11 ، ولكن لم يتم تسليمهما بالفعل في C99 (ظهروا بالفعل في وقت واحد مع C ++ في معيار C11) ، وبالتالي ، في المعايير القديمة ، يمكن تعريفها يكون فقط من خلال نوع مستعار (على سبيل المثال ،
typeedef short char16_t ، ولكن المزيد عن ذلك لاحقًا). إذا كان الأمر كذلك ، من أجل أن يتعامل تخصص القالب بشكل صحيح مع المواقف عندما تكون هذه الأنواع منفصلة (مضمنة) وعندما يتم تعريفها من خلال
الكتابة ، يلزم وجود طبقة أخرى من
تفاصيل تخصص القالب
:: _ is_كامل .
حقيقة مثيرة للاهتمام هي أنه في بعض المترجمين القدامى هذه الأنواع "الكبيرة" من C-shy ليست ثابتة لا يتجزأ . ما يمكن فهمه أو حتى مسامحته ، لأن هذه الأنواع غير قياسية لـ C ++ حتى 11 معيارًا ، وبشكل عام يجب ألا تكون هناك. ولكن ما يصعب فهمه هو أن هذه الأنواع في أحدث مترجم C ++ من إبداع Embarcadero (Embarcadero C ++ Builder) ، والذي يفترض أن يدعمه C ++ 11 ، لا يزال غير ثابتًا في تجميعات 32 بت (كما كان قبل 20 عامًا ثم كان بورلاند لا يزال صحيحا). على ما يبدو بسبب هذا ، بما في ذلك ، فإن معظم مكتبة C ++ 11 القياسية مفقودة في هذه التجميعات ذات 32 بت (#include ratio؟ Chrono؟ Will cost). يبدو أن Embarcadero قد قرر فرض عصر 64 بت مع شعار: "هل تريد C ++ 11 أو معيارًا أحدث؟ إنشاء برنامج 64 بت (و clang فقط ، لا يستطيع المترجم لدينا)! "
بعد الانتهاء من الإجراءات مع الأنواع الأساسية للغة ، نقدم بعض الأنماط الأكثر بساطة:
أنماط بسيطة template <bool, class _Tp = detail::void_type> struct enable_if { }; template <class _Tp> struct enable_if<true, _Tp> { typedef _Tp type; }; template<class, class> struct is_same : public false_type { }; template<class _Tp> struct is_same<_Tp, _Tp> : public true_type
فقط حقيقة أن القوالب متخصصة لجميع المعدلات من النوع (
المتقلب والثابت المتغير على سبيل المثال) هي جديرة بالملاحظة هنا ، لأن يميل بعض المترجمين إلى "فقدان" أحد المُعدِّلات عند توسيع القالب.
بشكل منفصل ، أسلط الضوء على تنفيذ
is_signed و
is_unsigned :
namespace detail { template<bool> struct _sign_unsign_chooser; template<class _Tp> struct _signed_comparer { static const bool value = _Tp(-1) < _Tp(0); }; template<class _Tp> struct _unsigned_comparer { static const bool value = _Tp(0) < _Tp(-1); }; template<bool Val> struct _cat_base : integral_constant<bool, Val> {
عند تنفيذ هذا الجزء ، دخلت في معركة غير متكافئة مع Borland C ++ Builder 6.0 ، والتي لم ترغب في جعل هذين القوالب هما ورثة
Integral_constant ، مما أدى في نهاية المطاف إلى عشرات من أخطاء المترجم الداخلية "محاكاة" سلوك
Integral_constant لهذه القوالب. هنا ، ربما ، من المفيد الاستمرار في القتال والتوصل إلى نوع من الاشتقاق الصعب من النوع
is_ * un * وقعت: Integral_constant من خلال القوالب ، ولكن حتى الآن لقد أجلت هذه المهمة باعتبارها ليست أولوية. ما هو مثير للاهتمام في قسم الكود أعلاه هو كيف أنه في وقت الترجمة يتم تحديد أن النوع غير موقع / موقّع. بادئ ذي بدء ، يتم
وضع علامة على جميع الأنواع غير الصحيحة
، وينقل القالب بالنسبة لهم إلى فرع متخصص منفصل
_sign_unsign_chooser مع وسيطة القالب
false ، والتي بدورها تُرجع دائمًا
القيمة == false لأي أنواع باستثناء أنواع
الفاصلة العائمة القياسية (فهي دائمًا مهمة لأسباب واضحة ، لذلك
_signed :: القيمة ستكون
صحيحة ). بالنسبة للأنواع الصحيحة ، يتم إجراء فحوصات بسيطة ولكنها مسلية إلى حد ما. هنا نستخدم حقيقة أنه بالنسبة لأنواع الأعداد الصحيحة غير الموقعة ، عندما ينخفض الرقم ثم يمر عبر الحد الأدنى (0 من الواضح) ، يحدث تجاوز ويكتسب الرقم أقصى قيمة ممكنة.
هذه الحقيقة معروفة جيدًا ، بالإضافة إلى حقيقة أن التدفق الزائد لأنواع الإشارات
هو سلوك غير محدد وتحتاج إلى مراقبته (وفقًا للمعيار ، لا يمكنك تقليل متغير
int أقل من
INT_MIN ونأمل أنه نتيجة
للتدفق الزائد تحصل على
INT_MAX ، وليس 42 أو قرص ثابت مهيأ )
نكتب
_Tp (-1) <_Tp (0) للتحقق من نوع "تسجيل" باستخدام هذه الحقيقة ، ثم بالنسبة للأنواع غير الموقعة -1 "تحويلات" من خلال تجاوز الحد الأقصى من هذا النوع ، بينما بالنسبة للأنواع الموقعة ، سيتم إجراء هذه المقارنة دون تجاوز السعة ، و -1 ستتم مقارنته مع 0.
وآخرها اليوم ، ولكن بعيدًا عن "الحيلة" الأخيرة في مكتبتي هو تنفيذ
المحاذاة :
namespace detail { template <class _Tp> struct _alignment_of_trick { char c; _Tp t; _alignment_of_trick(); }; template <unsigned A, unsigned S> struct _alignment_logic_helper { static const std::size_t value = A < S ? A : S; }; template <unsigned A> struct _alignment_logic_helper<A, 0> { static const std::size_t value = A; }; template <unsigned S> struct _alignment_logic_helper<0, S> { static const std::size_t value = S; }; template< class _Tp > struct _alignment_of_impl { #if _MSC_VER > 1400
تميزت Microsoft مرة أخرى هنا باستخدام Visual Studio الخاص بهم ، والذي لا يزال ينتج عنه ماكرو مدمج غير قياسي __alignof ، نتائج غير صحيحة عند استخدامه.
شرح من دفعةيجب على مستخدمي Visual C ++ ملاحظة أن MSVC لها تعريفات مختلفة لـ "المحاذاة". على سبيل المثال ، ضع في الاعتبار الرمز التالي:
typedef long long align_t; assert(boost::alignment_of<align_t>::value % 8 == 0); align_t a; assert(((std::uintptr_t)&a % 8) == 0); char c = 0; align_t a1; assert(((std::uintptr_t)&a1 % 8) == 0);
في هذا الكود ، على الرغم من أن تقارير التقارير عن boost :: alignment_of <align_t> تشير إلى أن align_t لديها محاذاة 8 بايت ، فإن التأكيد النهائي سيفشل لبناء 32 بت لأن a1 غير محاذاة على حد 8 بايت. لاحظ أننا لو استخدمنا __alignof MSVC بدلاً من التعزيز :: alignment_of ، فسوف نحصل على نفس النتيجة. في الواقع ، لمتطلبات محاذاة MSVC (والوعود) تنطبق فقط على التخزين الديناميكي ، وليس المكدس.
دعني أذكرك بما يجب أن يفعله القالب
std :: alignment_of - إرجاع قيمة تمثل متطلبات وضع عنصر من هذا النوع في الذاكرة. القليل من الإلهاء ، ثم يحتوي عنصر من كل نوع على نوع ما من تخصيص الذاكرة ، وإذا كان مستمرًا لمجموعة من العناصر ، فعلى سبيل المثال ، قد تحتوي الفئات على "ثقوب" بين العناصر الأعضاء في الفصل (حجم
بنية الفصل
{ char a؛} على الأرجح لن تساوي 1 ، على الرغم من وجود بايت واحد لكل شيء بداخله ، لأن المترجم سيحاذيه مع 1 + 3 بايت أثناء عملية التحسين).
الآن دعونا نلقي نظرة على الرمز مرة أخرى. نعلن بنية
_alignment_of_trick ، التي نضع فيها عنصرًا من النوع الذي يتم فحصه باستخدام "مسافة بادئة" في ذاكرة 1 بايت. وتحقق من المحاذاة ببساطة عن طريق طرح حجم النوع الذي يتم فحصه من حجم البنية الناتجة. بمعنى أنه إذا قرر المترجم "لصق" مسافة فارغة بين عنصر النوع الذي يتم فحصه
والحرف السابق ، فإننا نحصل على قيمة محاذاة النوع في الهيكل.
أيضا هنا تم العثور على التأكيد الثابت لأول مرة كنوع. يتم الإعلان عنها على أنها:
namespace intern {
في الواقع ، هناك حاجة إلى هذه القوالب المتخصصة لاستبدال
static_assert من C ++ 11 ، الموجود داخل تعريف الفئة. يعتبر هذا التأكيد أكثر خفة وأكثر تخصصًا من التنفيذ العام لـ
STATIC_ASSERT من
الفصل 2 ، ويسمح لك بعدم سحب قلب ملف الرأس.
h إلى
type_traits .

الكثير من الأنماط؟ سيكون هناك المزيد! سوف نتحدث عن هذا في الوقت الحالي ، حيث ستستمر القصة الرائعة حول الجمع بين برمجة القوالب مع تقنية SFINAE ، وكذلك سبب اضطراري إلى كتابة مولد رمز صغير.
شكرا لكم على اهتمامكم.