التمرير المثالي
جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 84 من 104.
عند كتابة template function تقبل argument وتمررها إلى function أخرى، تظهر مشكلة: كيف تحافظ على ما إذا كان argument الأصلي هو lvalue أم rvalue؟ يحل التمرير المثالي هذه المشكلة من خلال تمرير argument تمامًا كما تم استلامها، مع الحفاظ على فئة القيمة الخاصة بها.
العناصر الأساسية هي forwarding references (تُكتب بصيغة T&& في سياق القوالب) وstd::forward. يمكن لـ forwarding reference الارتباط بكلٍّ من lvalues وrvalues، وتُجري std::forward تحويلًا مشروطًا لـ argument إلى نوعه الأصلي:
#include <iostream>
#include <utility>
void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}
int main() {
int n = 10;
wrapper(n); // يستدعي process(int&) - يتم الحفاظ على lvalue
wrapper(20); // يستدعي process(int&&) - يتم الحفاظ على rvalue
}من دون std::forward، تكون المعاملات المُسمّاة دائمًا من نوع lvalues داخل الـ function، حتى إذا تم تمرير rvalue. وهذا سيمنع دلالات النقل من العمل بشكل صحيح عند التمرير إلى المُنشئات أو إلى functions أخرى.
يُعدّ التمرير المثالي (forwarding) ضروريًا لكتابة دوال Factory، وclass wrapper، وأي code عام يحتاج إلى إنشاء objects أو استدعاء function مع الحفاظ على نية المستدعي فيما يتعلق بـ move مقابل copy.
التحدي
سهللنَبْنِ دالة مصنع توضّح التمرير المثالي من خلال إنشاء كائنات مع الحفاظ على فئة القيمة للوسائط المُمرَّرة إليها. ستنشئ دالة عامة make_object تمرّر الوسائط إلى مُنشئ الفئة، مما يضمن نسخ قيم lvalue ونقل قيم rvalue.
ستنظّم التعليمة البرمجية عبر ثلاثة ملفات:
Widget.h: عرّف فئةWidgetتتتبّع كيفية استلامها لبياناتها.يجب أن تخزّن
Widgetاسمًا من النوعstd::stringوقيمة من النوعint. وفّر مُنشئَين:- مُنشئ يأخذ
const std::string&(مرجع lvalue) وint: ويطبعWidget constructed (copy): [name] - مُنشئ يأخذ
std::string&&(مرجع rvalue) وint: ويطبعWidget constructed (move): [name]وينقل السلسلة النصية
أضف أيضًا أسلوب
display()يطبع[name]: [value].- مُنشئ يأخذ
Factory.h: أنشئ دالة مصنع للتمرير المثالي.اكتب دالة قالب
make_objectتقبل مرجع تمرير للاسم وintعاديًا للقيمة. استخدمstd::forwardلتمرير وسيط الاسم إلى مُنشئWidgetمع الحفاظ على فئة قيمته الأصلية.يجب أن تعيد الدالة كائن
Widgetالمُنشأ بالقيمة.main.cpp: اقرأ مُدخلين:- اسمًا (سلسلة نصية)
- قيمة (عددًا صحيحًا)
وضّح التمرير المثالي من خلال إنشاء كائنَي Widget:
- أولًا، خزّن الاسم في متغير محلي ومرّره إلى
make_objectباعتباره lvalue - ثانيًا، مرّر سلسلة نصية مؤقتة (الاسم نفسه مضافًا إليه
"_temp") مباشرةً إلىmake_objectباعتبارها rvalue
بعد إنشاء كل كائن، استدعِ أسلوب
display()الخاص به.
على سبيل المثال، مع المُدخلين Gadget و42:
Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42ومع المُدخلين Device و100:
Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100الفكرة الأساسية هي أن دالة make_object لا تعرف ما إذا كانت قد تلقّت lvalue أم rvalue، ولكن باستخدام std::forward فإنها تحافظ على تلك المعلومة عند استدعاء مُنشئ Widget. يتيح ذلك لتحليل تحميلات المُنشئ الزائدة اختيار الإصدار المناسب، مما يفعّل عمليات النقل الفعّالة للقيم المؤقتة، مع نسخ المتغيرات المُسمّاة بأمان.
جرّب بنفسك
#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"
using namespace std;
int main() {
// اقرأ الإدخال
string name;
int value;
cin >> name >> value;
// TODO: أنشئ الـ widget الأول بتمرير name كـ lvalue
// خزّن الـ name في متغير محلي ومرّره إلى make_object
// TODO: استدعِ display() على الـ widget الأول
// TODO: أنشئ الـ widget الثاني بتمرير سلسلة نصية مؤقتة كـ rvalue
// مرّر (name + "_temp") مباشرة إلى make_object
// TODO: استدعِ display() على الـ widget الثاني
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة12ميزات C++ الحديثة
دلالات النقل و Rvaluesالتمرير المثاليتعبيرات Lambda بعمقstd::function و std::bindconstexpr و constevalالارتباطات المهيكلةoptional و variant و anyتدرّب بنفسك: مترجم C++ عبر الإنترنت