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

نعم - نعم ، مع هذا الشعار ، هرعت إلى المعركة.

بدلا من المقدمة


ربما مع هذه الصورة ، يجب أن تبدأ أي قصة عن التعزيز ، Loki ، المستقل ، وأيضًا تطبيقات مكتبة C ++ القياسية المزودة بمترجمين.

نعم ، نعم ، وإذا كنت تعتقد أن مطوري المكتبة القياسية لنفس g ++ أو clang أو Visual Studio أو ، الله يغفر لي ، فإن C ++ Builder (المعروف سابقًا باسم Borland ، ولكن Embarcadero الحالي) هم معلمو لا يصنعون العكازات ، فإنهم لا يكسرون معيار مترجمهم ولا تكتب دراجات ، فأنت على الأرجح لا تستخدم بنشاط مكتبة C ++ القياسية كما اعتقدت.

تم كتابة المقالة كقصة ، وتحتوي على الكثير من "الماء" والانبعاثات ، ولكن آمل أن تكون تجربتي والشفرة الناتجة مفيدة لأولئك الذين واجهوا مشاكل مماثلة عند التطوير في C ++ ، خاصة على المترجمين الأقدم. رابط إلى GitHub بالنتيجة لهذا اليوم للصبر وغير القراء:

https://github.com/oktonion/stdex (نرحب بالانتقادات والنقد البناء)

والآن ، أول شيء أولاً.


جدول المحتويات


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

الدخول


كان عام 2017 ، انطلقت C ++ 11 منذ فترة طويلة في تيار جديد من المترجمين الجدد والجدد نسبيًا ، مما جلب العمل المعياري مع التدفقات ، وكائنات المزامنة ، وتوسيع برمجة القوالب ، وتوحيد المقاربات ، وهناك أنواع طويلة "كبيرة" في المعيار ، وأخيرًا تخلص من الحاجة واسعة النطاق لعرض أنواع المترجم باستخدام auto (وداعًا std :: map <type، type> :: const_iterator it = ... - حسنًا ، أنت تفهمني) ، وأصبح الجمع بين هذه الميزة مع الجديدة لكل واحدة من أكثرها شيوعًا تستخدم تطبيقات حلقة المكرر. أخيرًا ، تمكنا نحن (المطورون) من إخبار المستخدم (المطور) بطريقة إنسانية عن سبب عدم جمع الشفرة باستخدام static_assert ، بالإضافة إلى enable_if ، الذي يحدد الآن الأحمال الزائدة اللازمة كما لو كان السحر.

في الفناء كان 2017! تم بالفعل تقديم C ++ 17 بنشاط في دول مجلس التعاون الخليجي ، clang ، Visual Studio ، في كل مكان كان هناك dectype (منذ C ++ 11) ، constexpr (منذ C ++ 11 ، ولكن تم تحسينه بشكل كبير) ، كانت الوحدات في الطريق تقريبًا ، كان هناك وقت جيد. كنت في العمل ومع بعض الرفض نظرت في خطأ المترجم الداخلي التالي في Borland C ++ Builder 6.0 ، بالإضافة إلى الكثير من أخطاء البناء مع الإصدار التالي من مكتبة التعزيز. أعتقد الآن أنك تفهم من أين جاءت هذه الرغبة في بناء الدراجات. استخدمنا Borland C ++ Builder 6.0 و Visual Studio 2010 لنظام التشغيل Windows ، إصدار g ++ 4.4.2 أو أقل لـ QNX وبعض أنظمة unix. لقد نجينا من نظام MacOS ، والذي كان بلا شك زائدًا. لا يمكن اعتبار أي مترجم آخر (بما في ذلك C ++ 11) لأسباب نتركها خارج هذه المقالة.

"وما يمكن أن يكون معقدًا جدًا هناك" - تسللت فكرة إلى محاولتي المنهكة لزيادة الدعم تحت عقل البناء القديم الجيد. "كل ما أحتاجه هو type_traits و thread و mutex وربما chrono و nullptr سيكون لطيفًا." استدركت واستعدت للعمل.

الفصل 1. فيام الإشراف على vadens


كان من الضروري البدء من حيث ، والبدء من حيث - بطبيعة الحال كان لدي عدد من ملفات العناوين ورموز المصدر المنتشرة عبر المشاريع مع تنفيذ وظائف مماثلة أو متطابقة من المكتبة القياسية C ++ 11 القياسية من تطوري ، بالإضافة إلى الاقتراض أو المعالجة بصراحة من رموز ذلك نفس دول مجلس التعاون الخليجي وتعزيز. بدمج كل هذا معًا ، حصلت على بعض الفوضى من الوظائف والفصول ووحدات الماكرو التي كان من المفترض أن تتحول إلى مكتبة قياسية أنيقة ونحيلة. بعد تقدير حجم العمل ، قررت على الفور التخلي عن تنفيذ كل شيء وكل شيء ، وقصر نفسي على تطوير "إضافة" على مكتبة C ++ 98 القياسية التي تأتي مع المترجم.

في الإصدار الأولي لم يكن هناك التزام خاص بالمعيار ، تم حل المشكلات المطبقة بشكل أساسي. على سبيل المثال ، بدا nullptr كما يلي:

#define nullptr 0 

تم حل static_assert أيضًا ببساطة:

  #define STATIC_ASSERT(expr) typedef int test##__LINE__##[expr ? 1 : -1]; 

تم تنفيذ std :: to_string من خلال std :: stringstream ، والذي تم استبداله بـ std :: strstream في عمليات التنفيذ بدون ملف رأس sstream ، وتم نقل كل هذا على الفور إلى مساحة الاسم std :

  #ifndef NO_STD_SSTREAM_HEADER #include <sstream> #else #include <strstream> namespace std {typedef std::strstream stringstream;} #endif namespace std { template<class T> string to_string(const T &t) { stringstream ss; ss << t; return ss.str(); } } 

كانت هناك أيضًا "حيل" لم يتم تضمينها في المعيار ، ولكنها مع ذلك مفيدة في العمل اليومي ، مثل إلى الأبد أو عدد وحدات الماكرو :

  #define forever for(;;) //     #define countof(arr) sizeof(arr) / sizeof(arr[0]) //        C 

ثم يتم تحويل countof إلى خيار C ++ أكثر:

  template <typename T, std::size_t N> char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N]; //        C++ (       ): #define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x)) 

تم تنفيذ العمل باستخدام مؤشرات الترابط ( مؤشر ترابط ملف الرأس من std) من خلال بعض مكتبات Tiny ، مع إعادة كتابتها مع مراعاة ميزات حديقة الحيوانات المجمّعة ونظام التشغيل بالكامل. وربما كان نوع type4raits إلى حد ما بالفعل مشابهًا لما يتطلبه معيار C ++ 11. كان هناك std :: enable_if و std :: Integral_constant و std :: is_const والقوالب المشابهة التي تم استخدامها بالفعل في التطوير.

  namespace std { template<bool Cond, class Iftrue, class Iffalse> struct conditional { typedef Iftrue type; }; // Partial specialization for false. template<class Iftrue, class Iffalse> struct conditional<false, Iftrue, Iffalse> { typedef Iffalse type; }; template <bool, class T = void> struct enable_if { }; template <class T> struct enable_if<true, T> { typedef T type; }; template<class Tp, Tp Val> struct integral_constant { // convenient template for integral constant types static const Tp value = Val; typedef const Tp value_type; typedef integral_constant<Tp, Val> type; }; typedef integral_constant<bool, true> true_type; typedef integral_constant<bool, false> false_type; template<bool Val> struct bool_constant : public integral_constant<bool, Val> { }; template<class, class> struct is_same : public false_type { }; template<class Tp> struct is_same<Tp, Tp> : public true_type // specialization { }; } // ...     

تقرر فصل جميع وحدات الماكرو والوظائف والأنواع غير القياسية و " المجمّعة" في ملف رأس رأس منفصل. h. وخلافا لممارسة التعزيز ، حيث يستخدم على نطاق واسع تبديل "تنفيذ" بمساعدة وحدات الماكرو ، للتخلي عن وحدات الماكرو المتعلقة بالأشياء المعتمدة على المترجم في جميع ملفات المكتبة ، باستثناء core.h. أيضًا ، الوظيفة التي لا يمكن تنفيذها دون استخدام "الاختراق" (انتهاك المعيار ، والاعتماد على سلوك غير محدد ليتم تعريفه إلى حد ما) ، أو يتم تنفيذه بشكل فردي لكل مترجم (من خلال وحدات الماكرو المضمنة ، على سبيل المثال) ، تقرر عدم إضافته إلى المكتبة ، حتى لا تنتج دفعة وحشية أخرى (ولكنها جميلة). ونتيجة لذلك ، فإن الشيء الرئيسي والعملي الوحيد الذي يتم استخدام core.h له هو تحديد ما إذا كان هناك دعم لـ nullptr المدمج (لأن المترجمين يقسمون إذا تجاوزوا الكلمات المحجوزة) ، ودعم static_assert المدمج (مرة أخرى ، لتجنب تقاطع كلمة محجوزة) ودعم الأنواع المضمنة C ++ 11 char16_t و char32_t .

بالنظر إلى المستقبل ، يمكنني القول إن الفكرة كانت ناجحة تقريبًا ، لأن معظم ما يتم تعريفه في التعزيز بواسطة وحدات الماكرو الصلبة اعتمادًا على مترجم معين ، في هذا التطبيق يتم تحديده بواسطة المترجم نفسه في مرحلة التجميع.

نهاية الفصل الأول. في الفصل الثاني ، سأستمر في السرد عن صعوبات التعامل مع المترجمين ، وعن العكازات التي تم العثور عليها والحلول الأنيقة في أحشاء دول مجلس التعاون الخليجي ، وتعزيز و Visual Studio ، بالإضافة إلى وصف انطباعاتي عما رأيته واكتسبت خبرة في أمثلة الشفرة.

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

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


All Articles