C++ / المتقدم / الدرس 13
المفاهيم والعقود العامة
قيّد الواجهات العامة بمفاهيم C++20، وميّز الصياغة المفحوصة من القوانين الدلالية، واجعل التشخيص قريبًا من المستدعي.
في هذه الصفحة
أهداف الدرس
- كتابة معاملات قوالب مقيدة وتعبيرات requires.
- شرح لماذا لا يثبت استيفاء الصياغة الدلالة.
- تصميم القيود وفق العمليات المستخدمة فعلًا.
رفض الأنواع غير المدعومة عند الواجهة
قد يفشل قالب غير مقيد داخل جسمه بعيدًا عن الاستدعاء عند غياب عملية مطلوبة. تنقل مفاهيم C++20 المتطلبات المناسبة إلى التصريح ليستبعد اختيار الدوال البدائل غير المناسبة مبكرًا. المفهوم شرط مسمى وقت الترجمة، وتربط requires-clause قيودًا بالتصريح. تصف القيود حاجة التنفيذ، ولا ينبغي أن تصبح قائمة خواص أنواع لا صلة بينها. فضّل المفاهيم القياسية عندما تطابق التجريد، وأضف مفهوم مجال مسمى فقط إذا حسّن العقد الذي يراه المستدعي.
قيد النوع لا يغني عن فحص القيمة
تقبل std::integral الأنواع الصحيحة بما فيها bool. تستبعد دالة المضاعفة المحصورة bool صراحة لأن مضاعفة قيمة منطقية لا تلائم معناها العددي المقصود. تفحص القيمة مقابل نصفي حدود نوعها قبل الضرب، فتمنع فيض الإشارة والتفاف غير ذي الإشارة للمدخلات المقبولة. يحدد المفهوم عائلة أنواع وتحدد فحوص التنفيذ مجال القيم. كلاهما لازم؛ فالنوع غير الصالح يُرفض في الترجمة والقيمة الخارجة عن المجال ترمي الاستثناء الموثق أثناء التنفيذ. يستخدم الاستدعاءان int وunsigned int العاديين.
#include <concepts>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <type_traits>
template<std::integral T> requires (!std::same_as<std::remove_cv_t<T>, bool>)
T twice_checked(T value) {
if (value > std::numeric_limits<T>::max() / 2 ||
value < std::numeric_limits<T>::min() / 2) {
throw std::out_of_range("doubling out of range");
}
return static_cast<T>(value * 2);
}
int main() {
std::cout << twice_checked(4) << " " << twice_checked(5u) << "\n";
}
8 10
تلائم القيمتان حدود نوعيهما. يُنفّذ الضرب بعد الفحص فقط ولا تُستخدم نتيجة فاضت لكشف الفيض.
وصف العمليات دون تنفيذها
يفحص تعبير requires صلاحية التعبيرات والمتطلبات المتداخلة دون تنفيذها. يسأل مفهوم الإبلاغ عن الحجم هل يملك الكائن الثابت تعبير size قابلًا للتحويل إلى std::size_t. يفحص ذلك شكل الواجهة لا صدق النتيجة أو كلفتها أو ثباتها أثناء تعديل متزامن. يطابق طلب الوصول الثابت جسم الدالة ولا يعد بقبول أنواع لا تتيح إلا وصولًا معدّلًا. لا تستخدم اسم مفهوم واحدًا لمعانٍ متعارضة عبر الوحدات. يمكن تركيب المتطلبات المسماة الصغيرة في واجهات أكبر مع وضوح التشخيص.
#include <concepts>
#include <cstddef>
#include <iostream>
#include <string>
#include <vector>
template<class T>
concept HasConstSize = requires(const T& value) {
{ value.size() } -> std::convertible_to<std::size_t>;
};
template<HasConstSize T>
std::size_t report_size(const T& value) { return value.size(); }
int main() {
const std::string text{"code"};
const std::vector<int> numbers{1, 2, 3};
std::cout << report_size(text) << " " << report_size(numbers) << "\n";
}
4 3
تستوفي السلسلة والمتجه الثابتان تعبير الحجم. تعد النتيجة عناصر كل نوع ولا تعرّف طول نص عامًا.
ذكر القوانين التي لا يثبتها المترجم
تفرض مفاهيم قياسية كثيرة متطلبات دلالية تتجاوز قبول التعبيرات في الترجمة. يجب أن تلتزم مقارنة الترتيب بقوانينه وأن تتصرف المساواة المعرفة من المستخدم باتساق مع التجريد. لا يثبت requires هذه القوانين لكل قيمة. تستطيع اختبارات الخصائص فحص حالات ممثلة مثل انعكاسية المساواة وتعدي المقارنة، ويحدد التوثيق الالتزام الكامل. لا تعرض المفهوم كبرهان صحة تام؛ فالشيفرة العامة تحتاج إلى فحص مجال القيم وقواعد الأعمار وضمانات الاستثناء والتعقيد كالشيفرة المحددة.
اتساق التحميل الزائد المقيد
قد يختار التحميل الزائد المقيد عملية أدق، لكن الشروط المنطقية المتداخلة الاعتباطية تصعب صيانتها وترتيبها. أعد استخدام مفاهيم مسماة بدل شروط متشابهة متناثرة، فللهوية والاستيعاب قواعد رسمية. حافظ على معنى العملية بين البدائل واختبر الأنواع المقبولة والمرفوضة عمدًا. ينتمي اختبار رفض الترجمة إلى إعداد اختبارات بناء، لا إلى مثال عام قابل للتشغيل يتظاهر بإنتاج إخراج. تحقق من دعم المترجم لميزات مفاهيم C++20 المستخدمة قبل تشخيص الاستدعاء بأنه غير مدعوم.
الأخطاء الشائعة وإصلاحها
استيفاء المفهوم مع فرز غير صالح.
- لماذا يحدث؟
- يُترجم المقارن لكنه يخالف قوانين الترتيب.
- كيف تصلحه؟
- أصلح دلالة المقارنة واختبر القوانين بحالات ممثلة.
- كيف تتجنب تكراره؟
- وثّق المتطلبات الدلالية بجانب القيود.
واجهة صحيحة تقبل bool دون قصد.
- لماذا يحدث؟
- تشمل std::integral النوع المنطقي.
- كيف تصلحه؟
- استبعد bool إذا تطلب المجال عدًّا عدديًا.
- كيف تتجنب تكراره؟
- راجع أعضاء المفهوم بأنواع حدية.
دالة مقيدة ما زالت تفيض.
- لماذا يحدث؟
- فُحصت الأنواع ولم تُفحص قيم التنفيذ.
- كيف تصلحه؟
- افحص الحدود قبل العملية الخطرة.
- كيف تتجنب تكراره؟
- افصل عقود الأنواع عن عقود القيم.
اختبر فهمك
1. هل يضمن HasConstSize أن size ثابتة الزمن؟ اشرح كيف تنقل هذا المتطلب إذا اعتمد عليه المستدعي.
اعرض الحل المشروح
لا، يفحص requires صلاحية التعبير وقابلية تحويل النتيجة فقط. وثّق شرط التعقيد ثابت الزمن منفصلًا واختر تجريدًا قياسيًا مناسبًا إن وجد، وقِس النماذج المقصودة. قد يستوفي نوع مخصص الصياغة مع اجتياز سلسلة مرتبطة كاملة؛ ولا يقيس قيد النوع خوارزميته.
2. صنّف صفرًا وmax()/2 وmax() للدالة twice_checked<int>، واشرح خطر فحص حاصل الضرب لاحقًا.
اعرض الحل المشروح
يُقبل الصفر ويعود صفرًا. تُقبل max()/2 وتتضاعف إلى عدد زوجي قابل للتمثيل. تُرفض max() بـout_of_range قبل الضرب. تنفيذ value * 2 أولًا يسبب فيضًا ذا إشارة عند max()، فلا تصلحه مقارنة لاحقة. أضف اختبارات حدود سالبة متناظرة للأنواع ذات الإشارة.
أهم ما تعلمته
- تحسن المفاهيم اختيار الواجهات والتشخيص.
- الصياغة والقوانين الدلالية ومجالات القيم التزامات منفصلة.
- قيّد العمليات التي يستخدمها التنفيذ فعلًا.