دلالات النقل و Rvalues
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 83 من 104.
في لغة ++C، كل تعبير هو إما lvalue (له هوية مستمرة، ويمكن الوصول إلى عنوانه) أو rvalue (مؤقت، وعلى وشك التدمير). فهم هذا التمييز يفتح الباب أمام move semantics (دلالات النقل) - وهو تحسين قوي يتجنب عمليات النسخ غير الضرورية.
يرتبط rvalue reference، الذي يتم التصريح عنه باستخدام &&، خصيصًا بالكائنات المؤقتة. يتيح لك هذا "سرقة" الموارد من الكائنات التي توشك على الاختفاء على أي حال:
#include <iostream>
#include <utility>
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : data(new int[s]), size(s) {
std::cout << "Constructed\n";
}
// منشئ النقل - يستحوذ على الموارد
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // اترك المصدر في حالة صالحة
other.size = 0;
std::cout << "Moved\n";
}
~Buffer() { delete[] data; }
};
int main() {
Buffer b1(1000);
Buffer b2(std::move(b1)); // يستدعي منشئ النقل
}لا تقوم وظيفة std::move بنقل أي شيء في الواقع - فهي ببساطة تقوم بتحويل lvalue إلى rvalue reference، مما يشير إلى أنك مستعد للتخلي عن موارد الكائن. عملية النقل الفعلية تحدث في move constructor أو move assignment operator.
تحسن دلالات النقل (Move semantics) الأداء بشكل كبير عند التعامل مع الكائنات كثيفة الاستهلاك للموارد مثل الحاويات أو السلاسل النصية. فبدلاً من النسخ العميق لميجابايت من البيانات، تقوم ببساطة بنقل ملكية المؤشر - وهي عملية ذات زمن ثابت بغض النظر عن الحجم.
التحدي
سهللنقم ببناء فئة DataBuffer لإدارة الموارد والتي توضح دلالات النقل (move semantics) بشكل عملي. سترى كيف أن نقل الموارد بدلاً من نسخها يمكن أن يحسن الكفاءة بشكل كبير عند نقل ملكية الذاكرة المخصصة ديناميكيًا.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
DataBuffer.h: قم بتعريف فئةDataBufferالتي تدير مصفوفة من الأعداد الصحيحة المخصصة ديناميكيًا.يجب أن تحتوي الفئة الخاصة بك على أعضاء خاصين (private) لمؤشر البيانات (
int*)، والحجم (size_t)، واسم (std::string) للمساعدة في تتبع أي مخزن مؤقت (buffer) هو المقصود أثناء العمليات.قم بالتصريح عما يلي:
- منشئ (constructor) يأخذ اسمًا من نوع
std::stringوحجمًا من نوعsize_t، ويخصص المصفوفة، ويطبع:[name] constructed with size [size] - منشئ نقل (move constructor) يأخذ مرجع rvalue، ويستولي على الموارد، ويطبع:
[name] moved from [source_name](حيث يأخذ المخزن المؤقت المنقول إليه اسم المصدر) - مدمر (destructor) يطبع
[name] destroyed(أوempty destroyedإذا تم النقل من المخزن المؤقت) - طريقة
getSize()تعيد الحجم الحالي - طريقة
getName()تعيد اسم المخزن المؤقت
تذكر أن تضع علامة
noexceptعلى منشئ النقل الخاص بك وتترك الكائن المصدر في حالة فارغة صالحة (nullptr، الحجم 0، والاسم "empty").- منشئ (constructor) يأخذ اسمًا من نوع
DataBuffer.cpp: قم بتنفيذ جميع الطرق المصرح عنها في الترويسة (header). عندما يعمل المدمر، قم بحذف البيانات فقط إذا لم يكن المؤشر فارغًا. قم بتضمين<iostream>للإخراج.main.cpp: اقرأ مدخلين:- اسم للمخزن المؤقت الخاص بك (string)
- حجم للمخزن المؤقت الخاص بك (integer)
قم بإنشاء
DataBufferبالاسم والحجم المعطيين. ثم قم بإنشاء مخزن مؤقت ثانٍ عن طريق النقل من الأول باستخدامstd::move(). بعد النقل، اطبع حالة كلا المخزنين المؤقتين:Original: [name] size=[size]New: [name] size=[size]
قم بتضمين
<utility>من أجلstd::move.
على سبيل المثال، مع المدخلات Alpha و 100:
Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyedمع المدخلات Buffer و 50:
Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyedلاحظ كيف يقوم منشئ النقل بنقل ملكية الذاكرة المخصصة دون نسخ أي بيانات. يُترك المخزن المؤقت الأصلي في حالة فارغة ولكنها صالحة، وعندما يتم تدمير كلا المخزنين المؤقتين في نهاية البرنامج، فإن المخزن الذي لا يزال يمتلك الذاكرة هو فقط من يقوم بحذفها فعليًا.
جرّب بنفسك
#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"
int main() {
std::string name;
int size;
std::cin >> name;
std::cin >> size;
// TODO: قم بإنشاء DataBuffer بالاسم والحجم المحددين
// TODO: قم بإنشاء مخزن مؤقت (buffer) ثانٍ عن طريق النقل من الأول باستخدام std::move()
// TODO: اطبع حالة كلا المخزنين المؤقتين:
// الأصلي: [name] size=[size]
// الجديد: [name] size=[size]
return 0;
}يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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