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

منشئ النسخ

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

يقوم منشئ النسخ (copy constructor) بإنشاء كائن جديد كنسخة من كائن موجود. يتم استدعاؤه عند تهيئة كائن من كائن آخر من نفس النوع، أو تمرير كائن بالقيمة، أو إرجاع كائن بالقيمة.

يأخذ منشئ النسخ (copy constructor) مرجعاً ثابتاً (const reference) لكائن من نفس الفئة:

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // منشئ النسخ
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // تم استدعاء منشئ النسخ
Player another(original);  // يستدعي أيضاً منشئ النسخ

إذا لم تقم بتعريف منشئ نسخ (copy constructor)، فإن المترجم (compiler) يقوم بإنشاء واحد يقوم بعملية نسخ سطحي (shallow copy) - حيث يقوم بنسخ قيمة كل عضو بشكل مباشر. يعمل هذا بشكل جيد مع الأنواع البسيطة، ولكنه يسبب مشاكل عندما تدير الفئة (class) الخاصة بك ذاكرة ديناميكية.

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // مُنشئ النسخ العميق
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // تخصيص ذاكرة جديدة
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // نسخ المحتويات
    }
    
    ~Buffer() { delete[] data; }
};

بدون منشئ نسخ مخصص (custom copy constructor)، سيشير كلا الكائنين إلى نفس الذاكرة. وعندما يتم تدمير أحدهما، سيبقى لدى الآخر مؤشر معلق (dangling pointer). أما النسخ العميق (deep copy) فيقوم بتخصيص ذاكرة جديدة ونسخ البيانات الفعلية، مما يضمن امتلاك كل كائن لموارده الخاصة.

challenge icon

التحدي

سهل

لنقم ببناء نظام لسجل الرسائل يوضح أهمية النسخ العميق (deep copying) عندما تدير فئتك (class) ذاكرة ديناميكية. ستقوم بإنشاء فئة MessageLog تخزن الرسائل في مصفوفة مخصصة ديناميكيًا، وتنفذ منشئ نسخ (copy constructor) مناسبًا لضمان أن كل نسخة تمتلك ذاكرتها المستقلة الخاصة بها.

ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:

  • MessageLog.h: قم بتعريف فئة MessageLog التي تدير مجموعة من الرسائل باستخدام الذاكرة الديناميكية. يجب أن تحتوي فئتك على:
    • أعضاء خاصين (Private members): مؤشر إلى مصفوفة سلاسل نصية (string array) لتخزين الرسائل، و capacity للحد الأقصى لعدد الرسائل، و count للعدد الحالي للرسائل.
    • منشئ بمعاملات (parameterized constructor) يأخذ السعة (capacity)، ويخصص المصفوفة في الذاكرة (heap)، ويهيئ count إلى 0.
    • منشئ نسخ (copy constructor) يقوم بعملية نسخ عميق (deep copy) — أي تخصيص ذاكرة جديدة ونسخ جميع الرسائل من الكائن المصدر.
    • مدمر (destructor) يحرر الذاكرة المخصصة ويطبع "MessageLog destroyed".
    • طريقة addMessage(string msg) تضيف رسالة إذا كانت هناك مساحة.
    • طريقة getCount() تعيد عدد الرسائل الحالي.
    • طريقة getMessage(int index) تعيد الرسالة الموجودة في الفهرس المعطى.
  • main.cpp: أثبت أن منشئ النسخ الخاص بك ينشئ نسخة مستقلة. اقرأ قيمة السعة ورسالتين من المدخلات، ثم:
    • أنشئ MessageLog يسمى original بالسعة المدخلة.
    • أضف كلتا الرسالتين إلى original.
    • أنشئ نسخة تسمى backup باستخدام منشئ النسخ.
    • أضف رسالة ثالثة "New message" إلى original فقط.
    • اطبع "Original count: <count>".
    • اطبع "Backup count: <count>".
    • اطبع جميع الرسائل من backup، كل واحدة في سطر.

تنسيق المدخلات سيكون:

  • السطر الأول: السعة (عدد صحيح).
  • السطر الثاني: الرسالة الأولى (سلسلة نصية).
  • السطر الثالث: الرسالة الثانية (سلسلة نصية).

إذا تم تنفيذ النسخ العميق بشكل صحيح، يجب أن يحتوي backup على رسالتين بينما يحتوي original على 3 رسائل — مما يثبت أنهما يمتلكان ذاكرة مستقلة. بدون منشئ نسخ مناسب، سيشترك كلاهما في نفس المصفوفة وستؤثر التغييرات في أحدهما على الآخر!

قم بتضمين ملف الرأس الخاص بك في main.cpp باستخدام #include "MessageLog.h".

ورقة مرجعية

يقوم منشئ النسخ (copy constructor) بإنشاء كائن جديد كنسخة من كائن موجود. وهو يأخذ مرجعاً ثابتاً (const reference) لكائن من نفس الفئة:

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // منشئ النسخ
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // تم استدعاء منشئ النسخ
Player another(original);  // يستدعي أيضاً منشئ النسخ

إذا لم تقم بتعريف منشئ نسخ، فسيقوم المترجم (compiler) بإنشاء واحد يقوم بعملية نسخ سطحي (shallow copy) - أي نسخ قيمة كل عضو مباشرة. يتسبب هذا في حدوث مشكلات عندما تدير فئتك ذاكرة ديناميكية.

يقوم منشئ النسخ العميق (deep copy constructor) بتخصيص ذاكرة جديدة ونسخ البيانات الفعلية، مما يضمن أن كل كائن يمتلك موارده الخاصة:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // منشئ النسخ العميق
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // تخصيص ذاكرة جديدة
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // نسخ المحتويات
    }
    
    ~Buffer() { delete[] data; }
};

بدون منشئ نسخ مخصص، سيشير كلا الكائنين إلى نفس الذاكرة، مما يتسبب في ظهور مؤشرات معلقة (dangling pointers) عند تدمير أحدهما.

جرّب بنفسك

#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;

int main() {
    // قراءة المدخلات
    int capacity;
    cin >> capacity;
    cin.ignore();
    
    string msg1, msg2;
    getline(cin, msg1);
    getline(cin, msg2);

    // TODO: قم بإنشاء MessageLog يسمى 'original' بسعة المدخلات

    // TODO: أضف كلتا الرسالتين إلى original

    // TODO: قم بإنشاء نسخة تسمى 'backup' باستخدام copy constructor

    // TODO: أضف "New message" إلى original فقط

    // TODO: اطبع "Original count: <count>"

    // TODO: اطبع "Backup count: <count>"

    // TODO: اطبع جميع الرسائل من backup، رسالة واحدة في كل سطر

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

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

جميع دروس Object Oriented Programming