Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

منشئ النقل

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 21 من 104.

يقوم منشئ النقل (move constructor) بنقل الموارد من كائن مؤقت بدلاً من نسخها. فبينما يقوم منشئ النسخ (copy constructor) بتكرار البيانات، يقوم منشئ النقل "بسرقة" الموارد، تاركاً الكائن المصدر في حالة صالحة ولكنها فارغة. يتجنب هذا عمليات النسخ العميق المكلفة عندما يكون الكائن المصدر على وشك التدمير على أي حال.

يأخذ منشئ النقل (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; }
};

الفرق الجوهري عن النسخ: نحن لا نخصص ذاكرة جديدة. نحن ببساطة نأخذ ملكية الذاكرة الموجودة ونقوم بتعيين مؤشر المصدر إلى nullptr حتى لا يقوم المدمر (destructor) الخاص به بحذف بياناتنا.

تُستدعى منشئات النقل (Move constructors) عند التهيئة من كائنات مؤقتة أو عند استخدام std::move():

Buffer createBuffer() {
    return Buffer(1000);    // تم استخدام منشئ النقل (قيمة الإرجاع)
}

Buffer b1(1000);
Buffer b2(std::move(b1));   // نقل صريح - b1 فارغ الآن

قم بتمييز منشئات النقل (move constructors) بـ noexcept عندما يكون ذلك ممكناً. هذا يخبر المترجم (compiler) أن العملية لن تطلق استثناءات، مما يتيح تحسينات مهمة في الحاويات (containers) مثل std::vector.

challenge icon

التحدي

سهل

لنقم ببناء نظام حزم بيانات يوضح كيف تقوم منشئات النقل (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 إذا كانت الحمولة فارغة).
  • 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().

جرّب بنفسك

#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
    // تلميح: استخدم std::move()

    // TODO: اطبع "After move:"

    // TODO: اطبع "Original - ID: <id>, Size: <size>, Sum: <sum>"

    // TODO: اطبع "Transferred - ID: <id>, Size: <size>, Sum: <sum>"

    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming