C++ / المتوسط / الدرس 10
القوالب وتعبيرات لامبدا والعمليات القابلة لإعادة الاستخدام
عمّم عملية مفيدة عبر الأنواع، ثم مرّر السلوك ككائن قابل للاستدعاء مع توضيح الحالة الملتقطة وعمرها.
في هذه الصفحة
أهداف الدرس
- قراءة قوالب الدوال وفهم الاستنتاج عند الاستدعاء.
- استخدام التقاط لامبدا بالقيمة وبالمرجع بوعي.
- إظهار متطلبات الشيفرة العامة وتعريفات القوالب.
عائلة دوال بدل تبديل نوع أثناء التنفيذ
يصف قالب الدالة عائلة دوال لها معاملات أنواع أو قيم. ينشئ المترجم تخصيصًا عندما يقتضي الاستخدام ذلك. يطابق استنتاج النوع أنماط المعاملات مع الوسائط ولا يحوّل كل الوسائط تلقائيًا إلى نوع مشترك ملائم. قد يرفض قالب بمعاملين من T عدد int وآخر double لأنهما يستنتجان نوعين مختلفين. اذكر العملية المطلوبة من T بدل الادعاء أن القالب يناسب كل الأنواع. يحتاج add_pair إلى جمع ذي معنى ونتيجة قابلة للتحويل إلى T.
تعريف واحد وتخصيصان صالحان
يستنتج الاستدعاء الأول T على أنه int والثاني std::string. يعني الجمع حسابًا عدديًا للأعداد ووصلًا للسلاسل؛ وتأتي الدلالتان من أنواع المعاملات. لا تسبب الأعداد المختارة فيضًا. لا يجعل القالب عملية خطرة آمنة ولا يثبت قوانينها الجبرية. وثّق أهمية الترتيب وإمكان رمي الاستثناء. تستطيع المفاهيم في الواجهات الفعلية التعبير عن بعض متطلبات الصياغة وخواص الأنواع، وتبقى الاختبارات والتوثيق مسؤولين عن التوقعات الدلالية التي لا يثبتها المترجم.
#include <iostream>
#include <string>
template<class T>
T add_pair(const T& left, const T& right) { return left + right; }
int main() {
std::cout << add_pair(4, 7) << "\n";
std::cout << add_pair(std::string{"sun"}, std::string{"rise"}) << "\n";
}
11
sunrise
يستنتج الوسيطان في كل استدعاء T نفسه، وتستخدم الأجسام المخصصة عملية الجمع الموافقة.
لامبدا كائن بحالة ملتقطة
ينشئ تعبير لامبدا كائن إغلاق له معامل استدعاء. يخزّن التقاط offset بالقيمة نسخة في الإغلاق، بينما تستعير الإشارة كائنًا قائمًا. لا يمدد الالتقاط بالمرجع عمره، فلا يجوز لرد نداء معاد استعارة محلي ميت. فضّل قوائم التقاط صريحة عندما تهم الملكية. ينسخ مثال transform إزاحة ثلاثة داخل لامبدا ثم يكتب عنصر إخراج لكل عنصر إدخال. يُضبط حجم الوجهة أولًا لأن transform تكتب عناصر قائمة ولا تلحقها تلقائيًا.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
const std::vector<int> input{1, 2, 3};
std::vector<int> output(input.size());
const int offset{3};
std::transform(input.begin(), input.end(), output.begin(),
[offset](int value) { return value + offset; });
for (int value : output) { std::cout << value << " "; }
std::cout << "done\n";
}
4 5 6 done
يزداد كل عدد صغير ثلاثة. يملك الإغلاق نسخة الإزاحة، ويملك متجه الإخراج ثلاثة عناصر قابلة للكتابة.
اختيار طريقة تمرير السلوك
يقبل معامل قالب أنواعًا مختلفة من الكائنات القابلة للاستدعاء، وغالبًا يتيح للمحسّن رؤية الهدف الدقيق. توفر std::function غلافًا مالكًا موحدًا للكائنات المتوافقة القابلة للنسخ في C++20، مع احتمال كلفة تخصيص واستدعاء غير مباشر. يستوعب مؤشر الدالة الدوال الحرة ولامبدا دون التقاط، لكنه لا يخزّن حالة ملتقطة عامة. اختر وفق العمر ومتطلبات الواجهة بدل السرعة المفترضة. إذا خرج رد النداء إلى طابور، يجب أن تبقى محتوياته صالحة حتى التنفيذ، وأن يحدد الطابور الإلغاء والفشل.
إتاحة التعريفات حيث تلزم
توضع تعريفات القوالب العادية غالبًا في الترويسات لتستطيع كل وحدة ترجمة إنشاء التخصيصات المستخدمة. يؤدي التصريح في ترويسة ووضع التعريف في مصدر منفصل عادة إلى تخصيصات مفقودة أثناء الربط. قد يدعم الإنشاء الصريح مجموعة أنواع مغلقة مقصودة، لكنه يحتاج إلى تصميم متسق. عند امتداد التشخيص عبر طبقات قوالب ابدأ باستدعاء المستخدم وأول عملية غير مستوفاة. اختزل المثال إلى أصغر نوع وكائن قابل للاستدعاء يعيدان المشكلة قبل إضافة تجريدات.
الأخطاء الشائعة وإصلاحها
رد نداء يقرأ متغيّرًا محليًا مدمرًا.
- لماذا يحدث؟
- غادر التقاط مرجعي نطاق مالكه.
- كيف تصلحه؟
- التقط قيمة مملوكة أو مقبض ملكية مناسبًا.
- كيف تتجنب تكراره؟
- راجع أعمار الالتقاط عند تخزين ردود النداء.
transform تكتب خارج وجهة فارغة.
- لماذا يحدث؟
- اعتُبرت reserve منشئة لعناصر الإخراج.
- كيف تصلحه؟
- غيّر حجم الوجهة أو استخدم مُدرجًا مناسبًا.
- كيف تتجنب تكراره؟
- أثبت صلاحية نطاق الكتابة قبل الاستدعاء.
تخصيص قالب غير معرّف أثناء الربط.
- لماذا يحدث؟
- لم يكن تعريفه متاحًا عند الإنشاء المطلوب.
- كيف تصلحه؟
- أظهر التعريف أو وفّر الإنشاء الصريح المقصود.
- كيف تتجنب تكراره؟
- اختبر ترويسات القوالب من وحدة ترجمة مستقلة.
اختبر فهمك
1. توقّع add_pair(2, 3) واشرح لماذا لا يلائم add_pair(2, 3.5) استنتاج T الواحد، ثم اقترح استدعاء مقصودًا.
اعرض الحل المشروح
يعيد الأول خمسة مع T من نوع int. يقترح الاستدعاء المختلط int وdouble معًا فيتعارض الاستنتاج. يختار add_pair<double>(2, 3.5) نوع double صراحة ويعيد 5.5 بعد التحويل. قرر هل تقبل الواجهة أنواعًا عددية مختلطة بدل إضافة تحويلات لإسكات الخطأ فقط.
2. يعيد مصنع لامبدا تضيف offset محليًا ملتقطًا بالمرجع. أعد تصميم الالتقاط وصف النتيجة لإزاحة أربعة ووسيط ستة.
اعرض الحل المشروح
التقط offset بالقيمة عبر [offset] فيخزن الإغلاق نسخته الصحيحة. لا تترك إعادته مرجعًا إلى محلي المصنع. يعيد استدعاؤه بستة عشرة لهذا الإدخال القابل للتمثيل. إذا لزم الاطلاع على تحديثات حالة مشتركة لاحقًا فهذا عقد مختلف يحتاج إلى تصميم عمر ومزامنة صريح.
أهم ما تعلمته
- تتطلب القوالب عمليات فعلية لا أنواعًا اعتباطية.
- الالتقاط قرار ملكية وعمر.
- وفّر تعريفات الإنشاء ونطاقات إخراج صالحة.