منشئ النقل
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 21 من 104.
يقوم منشئ النقل (move constructor) بنقل الموارد من كائن مؤقت بدلاً من نسخها. وبينما يقوم منشئ النسخ (copy constructor) بتكرار البيانات، فإن منشئ النقل "يسرق" الموارد، تاركاً الكائن المصدر في حالة صالحة ولكنها فارغة. وهذا يتجنب عمليات النسخ العميق المكلفة عندما يكون الكائن المصدر على وشك التدمير على أي حال.
يأخذ مُنشئ النقل مرجع rvalue، يُشار إليه بـ &&:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// منشئ النقل
Buffer(Buffer&& other) noexcept {
data = other.data; // الاستيلاء على المؤشر
size = other.size;
other.data = nullptr; // ترك المصدر في حالة صالحة
other.size = 0;
}
~Buffer() { delete[] data; }
};الفرق الجوهري عن النسخ: نحن لا نخصص ذاكرة جديدة. نحن ببساطة نأخذ ملكية الذاكرة الموجودة ونقوم بتعيين مؤشر المصدر إلى nullptr حتى لا يقوم المدمر (destructor) الخاص به بحذف بياناتنا.
تُستدعى منشئات النقل (Move constructors) عند التهيئة من كائنات مؤقتة أو عند استخدام std::move():
Buffer createBuffer() {
return Buffer(1000); // تم استخدام منشئ النقل (قيمة الإرجاع)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // نقل صريح - b1 الآن فارغقم بتمييز منشئات النقل (move constructors) بـ noexcept عند الإمكان. هذا يخبر المترجم أن العملية لن تطلق استثناءات، مما يتيح تحسينات مهمة في الحاويات مثل std::vector.
التحدي
سهللنقم ببناء نظام حزم بيانات يوضح كيف تقوم منشئات النقل (move constructors) بنقل ملكية الموارد بكفاءة. ستقوم بإنشاء فئة DataPacket تدير مصفوفة من البايتات مخصصة ديناميكيًا، وتنفذ منشئ نقل "يسرق" البيانات بدلاً من نسخها.
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
DataPacket.h: قم بتعريف فئةDataPacketتدير حمولة (payload) من بيانات الأعداد الصحيحة. يجب أن تحتوي الفئة على:- أعضاء خاصة (Private members): مؤشر إلى مصفوفة أعداد صحيحة (
payload)، وsizeلعدد العناصر، وpacketId(سلسلة نصية) لتعريف الحزمة. - منشئ بمعاملات (parameterized constructor) يأخذ معرف الحزمة وحجمها، ويخصص المصفوفة، ويملأها بقيم من 0 إلى size-1. اطبع
"Packet <id> created with size <size>". - منشئ نقل (move constructor) مميز بـ
noexceptينقل ملكية الحمولة من الكائن المصدر. اطبع"Packet <id> moved". تذكر أن تترك المصدر في حالة فارغة صالحة (nullptr، وحجم 0). - مدمر (destructor) يحرر الذاكرة إذا لم يكن المؤشر فارغًا، ويطبع
"Packet <id> destroyed". - طريقة
getSize()تعيد الحجم الحالي. - طريقة
getId()تعيد معرف الحزمة. - طريقة
getSum()تعيد مجموع كل العناصر في الحمولة (أعد 0 إذا كانت الحمولة فارغة).
- أعضاء خاصة (Private members): مؤشر إلى مصفوفة أعداد صحيحة (
main.cpp: وضح عملية إنشاء النقل من خلال قراءة معرف الحزمة وحجمها من المدخلات. ثم:- أنشئ
DataPacketيسمىoriginalبالقيم المدخلة. - اطبع
"Original - ID: <id>, Size: <size>, Sum: <sum>". - أنشئ حزمة جديدة تسمى
transferredعن طريق النقل منoriginalباستخدامstd::move(). - اطبع
"After move:". - اطبع
"Original - ID: <id>, Size: <size>, Sum: <sum>". - اطبع
"Transferred - ID: <id>, Size: <size>, Sum: <sum>".
- أنشئ
تنسيق المدخلات سيكون:
- السطر الأول: معرف الحزمة (string).
- السطر الثاني: الحجم (integer).
بعد النقل، يجب أن يكون للحزمة الأصلية حجم 0 ومجموع 0 (بما أن بياناتها قد نُقلت)، بينما يجب أن تحتوي الحزمة المنقولة على جميع البيانات الأصلية. يوضح هذا كيف تتجنب دلالات النقل (move semantics) النسخ العميق المكلف عن طريق نقل ملكية المؤشر ببساطة.
قم بتضمين ملف الرأس الخاص بك في main.cpp باستخدام #include "DataPacket.h" ولا تنسَ تضمين <utility> من أجل std::move().
ورقة مرجعية
يقوم منشئ النقل (move constructor) بنقل الموارد من كائن مؤقت بدلاً من نسخها، مما يتجنب عمليات النسخ العميق المكلفة. يأخذ مرجع rvalue (&&) و"يسرق" الموارد، تاركاً الكائن المصدر في حالة صالحة ولكن فارغة.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// منشئ النقل
Buffer(Buffer&& other) noexcept {
data = other.data; // سرقة المؤشر
size = other.size;
other.data = nullptr; // ترك المصدر في حالة صالحة
other.size = 0;
}
~Buffer() { delete[] data; }
};يتم استدعاء منشئي النقل عند التهيئة من كائنات مؤقتة أو عند استخدام std::move():
Buffer createBuffer() {
return Buffer(1000); // تم استخدام منشئ النقل (قيمة الإرجاع)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // نقل صريح - b1 الآن فارغقم بتمييز منشئي النقل بـ noexcept عندما يكون ذلك ممكناً لتمكين التحسينات في الحاويات القياسية.
جرّب بنفسك
#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"
using namespace std;
int main() {
// قراءة المدخلات
string packetId;
int size;
cin >> packetId;
cin >> size;
// TODO: قم بإنشاء DataPacket يسمى 'original' باستخدام قيم المدخلات
// TODO: اطبع "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: قم بإنشاء حزمة جديدة تسمى 'transferred' عن طريق النقل من original
// Hint: استخدم std::move()
// TODO: اطبع "After move:"
// TODO: اطبع "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: اطبع "Transferred - ID: <id>, Size: <size>, Sum: <sum>"
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مراجعة - الحاويات العامة