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

منشئ النقل

جزء من قسم 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.

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().

ورقة مرجعية

يقوم منشئ النقل (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;
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming