
في C ++ 20 ، ظهرت برمجة العقد. حتى الآن ، لم يطبق أي مترجم دعمًا لهذه الميزة.
ولكن هناك طريقة الآن لمحاولة استخدام العقود من C ++ 20 ، كما هو موضح في المعيار.
TL ؛ د
هناك رنة شوكة تدعم العقود. باستخدام مثاله ، أخبرك بكيفية استخدام العقود حتى تتمكن من البدء في استخدامها فور ظهور إحدى الميزات في برنامج التحويل البرمجي المفضل لديك.
لقد تم بالفعل كتابة الكثير عن برمجة العقود ، ولكن باختصار ، سوف أخبرك بما هو وما هو المقصود به.
منطق هور
يعتمد نموذج العقود على منطق هور ( 1 ، 2 ).
هوار المنطق هو وسيلة لإثبات صحة خوارزمية رسميا.
وهي تعمل مع مفاهيم مثل الشرط المسبق ، postcondition والثابت.
من وجهة نظر عملية ، يعد استخدام منطق هور ، أولاً ، وسيلة لإثبات صحة البرنامج بشكل رسمي في الحالات التي يمكن أن تؤدي فيها الأخطاء إلى كارثة أو خسائر في الأرواح. ثانيا ، طريقة لزيادة موثوقية البرنامج ، جنبا إلى جنب مع التحليل والاختبار ثابت.
برمجة العقود
( 1 ، 2 )
الفكرة الرئيسية للعقود هي أنه ، بالقياس للعقود في الأعمال التجارية ، يتم وصف الاتفاقات لكل وظيفة أو طريقة. يجب مراعاة هذه الترتيبات من قبل كل من المتصل والمتصل.
جزء لا يتجزأ من العقود هو وضعين للتجميع على الأقل - تصحيح الأخطاء والبقالة. يجب أن تتصرف العقود بشكل مختلف حسب وضع الإنشاء. الممارسة الأكثر شيوعًا هي التحقق من العقود في مجموعة تصحيح الأخطاء وتجاهلها في البقالة.
في بعض الأحيان يتم فحص العقود أيضًا في مجموعة المنتج ويمكن أن يؤدي عدم تنفيذها ، على سبيل المثال ، إلى إنشاء استثناء.
الفرق الرئيسي بين استخدام العقود من النهج "الكلاسيكي" هو أن المتصل يجب أن يمتثل للشروط المسبقة للمطلوب ، والتي تم وصفها في العقد ، ويجب على المتصل الالتزام بشروطه البريدية وغيره من المتغيرات.
وفقًا لذلك ، لا يُطلب من الطرف المتصل التحقق من صحة المعلمات الخاصة به. يتم تعيين هذا الالتزام للمتصل بموجب العقد.
يجب الكشف عن عدم الامتثال للعقود في مرحلة الاختبار ويكمل جميع أنواع الاختبارات: التكامل المعياري ، إلخ.
للوهلة الأولى ، استخدام العقود يجعل التطوير أكثر صعوبة ويقلل من قراءة الكود. في الواقع ، فإن العكس هو الصحيح. سيجد أتباع الكتابة الثابتة أنه من الأسهل تقييم فوائد العقود ، لأن الخيار الأسهل هو وصف الأنواع في توقيع الأساليب والوظائف.
إذن ما هي فوائد العقود:
- تحسين قراءة التعليمات البرمجية من خلال وثائق واضحة.
- تحسين موثوقية الرمز من خلال استكمال الاختبار.
- السماح للمترجمين باستخدام تحسينات المستوى المنخفض وإنشاء رمز أسرع استنادًا إلى توافق العقد. في الحالة الأخيرة ، قد يؤدي عدم الامتثال للعقد في مجموعة الإصدار إلى UB.
برمجة العقد في C ++
يتم تنفيذ برمجة العقود بعدة لغات. الأمثلة الأكثر وضوحا هي إيفل ، حيث تم تنفيذ النموذج لأول مرة ، و D ، في العقود D هي جزء من اللغة.
في C ++ ، قبل معيار C ++ 20 ، يمكن استخدام العقود كمكتبات منفصلة.
هذا النهج له عدة عيوب:
- بناء جملة أخرق للغاية باستخدام وحدات الماكرو.
- عدم وجود نمط واحد.
- عدم القدرة على استخدام العقود من قبل المترجم لتحسين الكود.
تستند تطبيقات المكتبة عادةً إلى استخدام التوجيهات القديمة الجيدة وتأكيدات المعالج الأولي التي تتحقق من علامة الترجمة.
إن استخدام العقود في هذا النموذج يجعل الكود قبيحًا وغير قابل للقراءة. هذا هو أحد الأسباب التي تجعل استخدام العقود في C ++ قليل الممارسة.
في المستقبل ، سأظهر كيف سيبدو استخدام العقود في C ++ 20.
وبعد ذلك ، سوف نحلل كل هذا بمزيد من التفصيل:
int f(int x, int y) [[ expects: x > 0 ]]
جرب
لسوء الحظ ، في الوقت الحالي ، لم يقم أي من المجمعين على نطاق واسع بتنفيذ دعم العقود.
ولكن هناك طريقة للخروج.
نفذت مجموعة أبحاث ARCOS من جامعة كارلوس الثالث دي مدريد الدعم التجريبي للعقود في شوكة clang ++.
من أجل "كتابة التعليمات البرمجية على قطعة من الورق" ، ولكن حتى نتمكن من تجربة الفرص الجديدة في مجال الأعمال التجارية على الفور ، يمكننا جمع هذه الشوكة واستخدامها لتجربة الأمثلة أدناه.
يتم وصف تعليمات التجميع في الملف التمهيدي لمستودع جيثب
https://github.com/arcosuc3m/clang-contracts
git clone https://github.com/arcosuc3m/clang-contracts/ mkdir -p clang-contracts/build/ && cd clang-contracts/build/ cmake -G "Unix Makefiles" -DLLVM_USE_LINKER=gold -DBUILD_SHARED_LIBS=ON -DLLVM_USE_SPLIT_DWARF=ON -DLLVM_OPTIMIZED_TABLEGEN=ON ../ make -j8
لم أواجه أي مشكلة أثناء التجميع ، لكن تجميع المصادر يستغرق وقتًا طويلاً للغاية.
لتجميع الأمثلة ، ستحتاج إلى تحديد المسار إلى clang ++ الثنائية بشكل صريح.
على سبيل المثال ، يبدو لي مثل هذا
/home/valmat/work/git/clang-contracts/build/bin/clang++ -std=c++2a -build-level=audit -g test.cpp -o test.bin
أعددت أمثلة لجعلها ملائمة لك لفحص العقود باستخدام أمثلة التعليمات البرمجية الحقيقية. أقترح ، قبل البدء في قراءة القسم التالي ، استنساخ وتجميع أمثلة.
git clone https://github.com/valmat/cpp20-contracts-examples/ cd cpp20-contracts-examples make CPP=/path/to/clang++
هنا /path/to/clang++
المسار إلى clang++
الثنائية لتجميع برنامج التحويل البرمجي التجريبي.
بالإضافة إلى المترجم نفسه ، أعدت مجموعة أبحاث ARCOS نسختها من Compiler Explorer لشوكةها.
برمجة العقود في C ++ 20
الآن لا شيء يمنعنا من البدء في البحث عن الإمكانيات التي توفرها برمجة العقود ، وتجربة هذه الفرص على الفور في الممارسة العملية.
كما ذكر أعلاه ، يتم بناء العقود من الشروط المسبقة ، الشروط المسبقة والمتغيرات (البيانات).
في C ++ 20 ، يتم استخدام السمات ذات بناء الجملة التالي لهذا الغرض
[[contract-attribute modifier identifier: conditional-expression]]
حيث يمكن أن تأخذ contract-attribute
إحدى القيم التالية:
تتوقع ، يضمن أو تؤكد .
expects
يستخدم للشروط المسبقة ، ensures
ل postconditions assert
للبيانات.
conditional-expression
هو conditional-expression
منطقي يتم التحقق منه في مسند العقد.
قد يتم حذف modifier
والمعرّف.
لماذا أحتاج إلى modifier
سأكتب أقل قليلاً.
يستخدم identifier
فقط مع ensures
ويتم استخدامه لتمثيل قيمة الإرجاع.
الشروط المسبقة لها حق الوصول إلى الحجج.
يمكن لـ postconditions الوصول إلى القيمة التي يتم إرجاعها بواسطة الوظيفة. يستخدم بناء الجملة لهذا الغرض.
[[ensures return_variable: expr(return_variable)]]
حيث return_variable
أي تعبير صالح للمتغير.
بمعنى آخر ، تهدف الشروط المسبقة إلى إعلان القيود المفروضة على الحجج التي تقبلها الوظيفة ، والشروط المسبقة لإعلان القيود المفروضة على القيمة التي أرجعتها الوظيفة.
من المعتقد أن الشروط المسبقة وشروط البريد هي جزء من واجهة الوظيفة ، في حين أن البيانات هي جزء من تنفيذها.
يتم تقييم تقديرات الشروط المسبقة دائمًا فور تنفيذ الوظيفة. يتم استيفاء الشروط البريدية فور انتقال وظيفة التحكم إلى رمز الاتصال.
إذا تم طرح استثناء في إحدى الوظائف ، فلن يتم التحقق من حالة التأجيل.
لا يتم التحقق من الشروط البريدية إلا إذا كانت الوظيفة مكتملة بشكل طبيعي.
في حالة حدوث استثناء أثناء التحقق من التعبير في العقد ، سيتم استدعاء std::terminate()
.
يتم دائمًا وصف الشروط المسبقة والشروط التالية خارج نص الوظيفة ولا يمكن الوصول إلى المتغيرات المحلية.
إذا وصفت الشروط المسبقة والشروط التالية عقدًا لطريقة الطبقة العامة ، فلن تتمكن من الوصول إلى حقول الفصل الخاصة والمحمية. إذا كانت طريقة الفصل محمية ، فهناك إمكانية الوصول إلى البيانات المحمية والعامة للفصل ، ولكن ليس إلى القطاع الخاص.
القيد الأخير منطقي تمامًا ، نظرًا لأن العقد جزء من واجهة الطريقة.
يتم دائمًا وصف العبارات (المتغيرات) في نص دالة أو طريقة. حسب التصميم ، فهي جزء من التنفيذ. وبالتالي ، يمكنهم الوصول إلى جميع البيانات المتاحة. بما في ذلك متغيرات الوظيفة المحلية وحقول الطبقة الخاصة والمحمية.
مثال 1
نحن نعرّف شرطين مسبقين ، أحد الشروط التالية والآخر ثابت:
int foo(int x, int y) [[ expects: x > y ]]
مثال 2
لا يمكن أن يشير شرط مسبق للطريقة العامة إلى حقل محمي أو خاص:
struct X {
لا يُسمح بتعديل المتغيرات ضمن التعبيرات الموصوفة في سمات العقد. إذا تم كسره ، سيكون هناك UB.
يجب ألا يكون للتعبيرات الموضحة في العقود آثار جانبية. على الرغم من أن المجمعين يمكنهم التحقق من ذلك ، إلا أنه ليس مطلوبًا منهم القيام بذلك. يعتبر انتهاك هذا المتطلب سلوكًا غير محدد.
struct X { int m = 5; int foo(int n) [[ expects: n < m++ ]]
سيصبح شرط عدم تغيير حالة البرنامج في تعبيرات العقود أقل بقليل عندما أتحدث عن مستويات معدّلات العقود وأساليب البناء.
ألاحظ الآن أن البرنامج الصحيح يجب أن يعمل كما لو لم تكن هناك عقود على الإطلاق.
كما أشرت أعلاه ، في العقد يمكنك تحديد العديد من الشروط المسبقة وشروط البريد التي تريدها.
وسيتم فحص كل منهم في النظام. لكن يتم دائمًا التحقق من الشروط المسبقة قبل تنفيذ الوظيفة ، والظروف البريدية مباشرة بعد الخروج منها.
هذا يعني أنه يتم دائمًا التحقق من الشروط المسبقة أولاً ، كما هو موضح في المثال التالي:
int foo(int n) [[ expects: expr(n) ]]
يمكن أن تشير التعبيرات في postconditions ليس فقط إلى القيمة التي يتم إرجاعها بواسطة الدالة ، ولكن أيضًا إلى وسيطات الدالة.
int foo(int &n) [[ ensures: expr(n) ]];
في هذه الحالة ، يمكنك حذف معرف قيمة الإرجاع.
إذا كان يشير postcondition إلى وسيطة الوظيفة ، فسيتم اعتبار هذه الوسيطة عند نقطة الخروج من الوظيفة ، وليس عند نقطة الإدخال ، كما هو الحال مع الشروط المسبقة.
لا توجد وسيلة للإشارة إلى القيمة الأصلية (عند نقطة إدخال الوظيفة) في حالة النقل.
مثال :
void incr(int &n) [[ expects: 3 == n ]] [[ ensures: 4 == n ]] {++n;}
يمكن أن تشير المسندات في العقود إلى المتغيرات المحلية فقط إذا كان عمر هذه المتغيرات يتوافق مع وقت الحساب المسند.
على سبيل المثال ، بالنسبة لوظائف constexpr
، لا يمكن الرجوع إلى المتغيرات المحلية ما لم تكن معروفة في وقت الترجمة.
مثال :
int a = 1; constexpr int b = 100; constexpr int foo(int n) [[ expects: a <= n ]]
عقود مؤشرات الوظائف
لا يمكنك تحديد العقود لمؤشر دالة ، ولكن يمكنك تعيين عنوان دالة يتم تعريف العقد لها بمؤشر دالة.
مثال :
int foo(int n) [[expects: n < 10]] { return n*n; } int (*pfoo)(int n) = &foo;
استدعاء pfoo(100)
سوف ينتهك العقد.
عقود الوراثة
يشير التنفيذ الكلاسيكي لمفهوم العقود إلى أنه يمكن إضعاف الشروط المسبقة في الفئات الفرعية ، ويمكن تعزيز الشروط المسبقة والثوابت في الفئات الفرعية.
في تطبيق C ++ 20 ، هذا ليس هو الحال.
أولاً ، يعد المتحولون في C ++ 20 جزءًا من تطبيق ، وليس واجهة. لهذا السبب ، يمكن تعزيزهما وإضعافهما. إذا لم يكن هناك assert
في تنفيذ الوظيفة الافتراضية ، فلن يتم توريثها.
ثانياً ، من المطلوب أن تكون ODR متطابقة عند توريث الوظائف.
ونظرًا لأن الشروط المسبقة وشروط البريد هي جزء من الواجهة ، فيجب أن تتطابق تمامًا في الوريث.
علاوة على ذلك ، يمكن حذف وصف الشروط المسبقة والشروط التالية أثناء الميراث. ولكن إذا تم التصريح عنها ، فيجب أن يطابقوا التعريف في الفئة الأساسية تمامًا.
مثال :
struct Base { virtual int foo(int n) [[ expects: n < 10 ]] [[ ensures r: r > 100 ]] { return n*n; } }; struct Derived1 : Base { virtual int foo(int n) override [[ expects: n < 10 ]] [[ ensures r: r > 100 ]] { return n*n*2; } }; struct Derived2 : Base {
ملاحظةلسوء الحظ ، لا يعمل المثال أعلاه في المحول البرمجي التجريبي كما هو متوقع.
إذا Derived2
foo
من Derived2
العقد ، فلن يتم توريثه من الفئة الأساسية. بالإضافة إلى ذلك ، يسمح لك برنامج التحويل البرمجي بتحديد عقد لا يتطابق مع العقد الأساسي لفئة فرعية.
خطأ مترجم تجريبي آخر:
يجب أن يكون السجل صحيحًا من الناحية النحوية
virtual int foo(int n) override [[expects: n < 10]] {...}
ومع ذلك ، في هذا النموذج ، تلقيت خطأ ترجمة
inheritance1.cpp:20:36: error: expected ';' at end of declaration list virtual int foo(int n) override ^ ;
وكان لا بد من استبداله
virtual int foo(int n) [[expects: n < 10]] override {...}
أعتقد أن هذا يرجع إلى خصوصية المترجم التجريبي ، وستعمل التعليمات البرمجية الصحيحة بناء الجملة في إصدارات إصدار المجمعين.
معدلات العقد
يمكن أن تتكبد الشيكات المسندة إلى العقود تكاليف معالجة إضافية.
لذلك ، من الممارسات الشائعة التحقق من العقود في التطوير واختبار البنيات وتجاهلها في إصدار الإصدار.
لهذه الأغراض ، يوفر المعيار ثلاثة مستويات من معدّلات العقود. باستخدام المعدلات ومفاتيح التحويل البرمجي ، يمكن للمبرمج التحكم في جهات الاتصال التي يتم فحصها في التجميع والتي يتم تجاهلها.
default
- يستخدم هذا المعدل افتراضيًا. من المفترض أن تكون التكلفة الحسابية للتحقق من تنفيذ تعبير باستخدام هذا المعدل صغيرة مقارنةً بتكلفة حساب الوظيفة نفسها.audit
- يفترض هذا المعدل أن التكلفة الحاسوبية للتحقق من تنفيذ تعبير مهم مقارنة بتكلفة حساب الوظيفة نفسها.axiom
- يستخدم هذا المعدل إذا كان التعبير تعريفي. لم يتم التحقق في وقت التشغيل. يخدم لتوثيق واجهة وظيفة ، لاستخدامها من قبل محللات ثابتة ومحسن مترجم. لا axiom
تقييم التعبيرات باستخدام معدّل axiom
في وقت التشغيل.
مثال
[[expects: expr]]
باستخدام المعدلات ، يمكنك تحديد عمليات التحقق في إصدارات التجميعات التي سيتم استخدامها والتي سيتم تعطيلها.
تجدر الإشارة إلى أنه حتى في حالة عدم إجراء الفحص ، يحق للمترجم استخدام العقد لتحسين المستوى المنخفض. على الرغم من أن التحقق من العقد يمكن تعطيله بواسطة علامة الترجمة ، فإن خرق العقد يؤدي إلى سلوك غير محدد في البرنامج.
بناءً على تقدير المترجم ، يمكن توفير تسهيلات لتمكين axiom
التعبيرات التي تحمل علامة axiom
.
في حالتنا ، هذا خيار مترجم
-axiom-mode=<mode>
-axiom-mode=on
تشغيل وضع البديهية ، وبالتالي ، يتم إيقاف التحقق من المطالبات باستخدام axiom
،
-axiom-mode=off
تشغيل وضع axiom ، وبالتالي ، يتيح التحقق من البيانات باستخدام axiom
المعرف.
مثال :
int foo(int n) [[expects axiom: n < 10]] { return n*n; }
يمكن تجميع البرنامج مع ثلاثة مستويات مختلفة من التحقق:
off
إيقاف تشغيل جميع عمليات تدقيق التعبير في العقود- يتم تحديد التعبيرات
default
فقط مع المعدل default
audit
الوضع المتقدم عند إجراء جميع عمليات الفحص باستخدام معدِّل audit
ومعدل audit
كيف يتم تنفيذ تثبيت مستوى التحقق بالضبط وفقًا لتقدير مطوري برنامج التحويل البرمجي.
في حالتنا ، يتم استخدام خيار برنامج التحويل البرمجي لهذا الغرض
-build-level=<off|default|audit>
الافتراضي هو -build-level=default
كما سبق ذكره ، يمكن للمترجم استخدام العقود لتحسين المستوى المنخفض. لهذا السبب ، على الرغم من حقيقة أنه في وقت التنفيذ ، قد لا يتم احتساب بعض التنبؤات في العقود (حسب مستوى التحقق) ، يؤدي عدم الوفاء بها إلى سلوك غير محدد.
سأؤجل أمثلة على تطبيق مستويات التجميع حتى القسم التالي ، حيث يمكن جعلها مرئية.
اعتراض خرق العقد
اعتمادًا على الخيارات التي سيستخدمها البرنامج ، في حالة الإخلال بالعقد ، قد تكون هناك سيناريوهات سلوك مختلفة.
بشكل افتراضي ، يؤدي خرق العقد إلى تعطل البرنامج أو استدعاء std::terminate()
. لكن يمكن للمبرمج تجاوز هذا السلوك من خلال توفير معالج خاص به والإشارة إلى المترجم أنه من الضروري متابعة البرنامج بعد خرق العقد.
عند التحويل البرمجي ، يمكنك تثبيت معالج انتهاك ، والذي يسمى عند انتهاك العقد.
طريقة تنفيذ تثبيت المعالج هي حسب تقدير منشئي المحول البرمجي.
في حالتنا ، هذا
-contract-violation-handler=<violation_handler>
يجب أن يكون توقيع المعالج
void(const std::contract_violation& info)
أو
void(const std::contract_violation& info) noexcept
std::contract_violation
تعادل التعريف التالي:
struct contract_violation { uint_least32_t line_number() const noexcept; std::string_view file_name() const noexcept; std::string_view function_name() const noexcept; std::string_view comment() const noexcept; std::string_view assertion_level() const noexcept; };
وبالتالي ، يسمح لك المعالج بالحصول على معلومات شاملة تمامًا حول مكان وتحت أي ظروف وقعت انتهاك العقد.
إذا تم تحديد معالج معالج الانتهاك ، في حالة مخالفة العقد ، سيتم استدعاء std::abort()
افتراضيًا فور تنفيذه (دون تحديد المعالج ، سيتم استدعاء std::terminate()
).
يفترض المعيار أن المجمعين يوفرون أدوات تسمح للمبرمجين بمواصلة تنفيذ البرنامج بعد خرق العقد.
طريقة تنفيذ هذه الأدوات متروك لمطوري برنامج التحويل البرمجي.
في حالتنا ، هذا خيار مترجم
-fcontinue-after-violation
يمكن تعيين -contract-violation-handler
-fcontinue-after-violation
و -fcontinue-after-violation
بشكل مستقل عن بعضها البعض. على سبيل المثال ، يمكنك ضبط -fcontinue-after-violation
، ولكن لا يمكنك ضبط -fcontinue-after-violation
-contract-violation-handler
. في الحالة الأخيرة ، بعد خرق العقد ، سيستمر البرنامج ببساطة في العمل.
يتم تحديد القدرة على متابعة البرنامج بعد خرق العقد من قبل المعيار ، ولكن يجب توخي الحذر مع هذه الميزة.
من الناحية الفنية ، لم يتم تعريف سلوك البرنامج بعد خرق العقد ، حتى إذا أشار المبرمج صراحة إلى أن البرنامج يجب أن يستمر في العمل.
ويرجع ذلك إلى أن المترجم قادر على أداء تحسينات منخفضة المستوى بناءً على تنفيذ العقد.
من الناحية المثالية ، في حالة حدوث خرق للعقد ، تحتاج إلى تسجيل معلومات التشخيص في أقرب وقت ممكن وإنهاء البرنامج. تحتاج إلى فهم ما تقوم به بالضبط من خلال السماح للبرنامج بالعمل بعد الانتهاك.
حدد معالجك واستخدمه لاعتراض خرق العقد
void violation_handler(const std::contract_violation& info) { std::cerr << "line_number : " << info.line_number() << std::endl; std::cerr << "file_name : " << info.file_name() << std::endl; std::cerr << "function_name : " << info.function_name() << std::endl; std::cerr << "comment : " << info.comment() << std::endl; std::cerr << "assertion_level : " << info.assertion_level() << std::endl; }
والنظر في مثال على خرق العقد:
#include "violation_handler.h" int foo(int n) [[expects: n < 10]] { return n*n; } int main() { foo(100);
نقوم بتجميع البرنامج باستخدام الخيارات -fcontinue-after-violation
-contract-violation-handler=violation_handler
-fcontinue-after-violation
و -fcontinue-after-violation
وتشغيل
$ bin/example8-handling.bin line_number : 4 file_name : example8-handling.cpp function_name : foo comment : n < 10 assertion_level : default
الآن يمكننا تقديم أمثلة توضح سلوك البرنامج في حالة خرق العقد على مستويات التجميع المختلفة وأنماط العقد.
النظر في المثال التالي:
#include "violation_handler.h" int foo(int n) [[ expects axiom : n < 100 ]] [[ expects default : n < 200 ]] [[ expects audit : n < 300 ]] { return 2 * n; } int main() { foo(350);
إذا قمت -build-level=off
باستخدام الخيار -build-level=off
، ثم كما هو متوقع ، لن يتم التحقق من العقود.
بالتجمع مع المستوى default
(مع الخيار -build-level=default
) ، نحصل على المخرجات التالية:
$ bin/example9-default.bin line_number : 5 file_name : example9.cpp function_name : foo comment : n < 200 assertion_level : default line_number : 5 file_name : example9.cpp function_name : foo comment : n < 200 assertion_level : default
والتجمع مع مستوى audit
سيعطي:
$ bin/example9-audit.bin line_number : 5 file_name : example9.cpp function_name : foo comment : n < 200 assertion_level : default line_number : 6 file_name : example9.cpp function_name : foo comment : n < 300 assertion_level : audit line_number : 5 file_name : example9.cpp function_name : foo comment : n < 200 assertion_level : default
ملاحظات
violation_handler
قد يلقي استثناءات. في هذه الحالة ، يمكنك تكوين البرنامج بحيث يؤدي انتهاك العقد إلى طرح استثناء.
إذا تم وضع علامة على الوظيفة الموضحة للعقود على أنها noexcept
وعند استدعاء العقد noexcept
يسمى ، والذي يلقي استثناء ، ثم سيتم استدعاء std::terminate()
.
مثال
void violation_handler(const std::contract_violation&) { throw std::exception(); } int foo(int n) noexcept [[ expects: n > 0 ]] { return n*n; } int main() { foo(0);
إذا تم تمرير العلامة إلى برنامج التحويل البرمجي: لا تستمر في تنفيذ البرنامج بعد كسر العقد ( continuation mode=off
) ، لكن معالج الانتهاك يلقي استثناءً ، ثم يتم فرض std::terminate()
.
استنتاج
تتعلق العقود بالتحققات غير التدخلية وقت التشغيل. أنها تلعب دورا هاما للغاية في ضمان جودة البرمجيات التي تم إصدارها.
يستخدم C ++ على نطاق واسع جدا. وبالتأكيد سيكون هناك عدد كاف من المطالبات لتحديد العقود. في رأيي الشخصي ، اتضح أن التنفيذ مريح للغاية وبصري.
ستجعل عقود C ++ 20 برامجنا أكثر موثوقية وسريعة ومفهومة. وإنني أتطلع إلى تنفيذها في المجمعين.
PS
في PM ، يخبرونني أنه ربما في الإصدار النهائي من المعيار expects
ensures
سيتم استبداله pre
وبعد ، على التوالي.