Menu
CoddyTech

منشئ النسخ

جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 20 من 104.

ينشئ مُنشئ النسخ كائنًا جديدًا باعتباره نسخة من كائن موجود. ويُستدعى عند تهيئة كائن من كائن آخر من النوع نفسه، أو تمرير كائن حسب القيمة، أو إرجاع كائن حسب القيمة.

يستقبل مُنشئ النسخ مرجعًا ثابتًا لكائن من الفئة نفسها. يجب أن يكون مرجعًا: فتمرير other بالقيمة سيتطلب بحد ذاته إنشاء نسخة، ما يؤدي إلى استدعاء مُنشئ النسخ مرارًا إلى الأبد. ويكون const لأن إنشاء نسخة يجب ألا يغيّر الأصل أبدًا:

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);  // يستدعي مُنشئ النسخ أيضًا

إذا لم تعرّف مُنشئ نسخ، فسيُنشئ المترجم واحدًا يُجري نسخًا سطحيًا - إذ ينسخ قيمة كل عضو مباشرةً. يعمل هذا جيدًا مع الأنواع البسيطة، لكنه يسبب مشكلات عندما تدير فئتك ذاكرة ديناميكية.

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; }
};

من دون مُنشئ نسخ مخصّص، سيشير الكائنان إلى الذاكرة نفسها. وعندما يُدمَّر أحدهما، سيصبح لدى الآخر مؤشر معلّق. تقوم النسخة العميقة بتخصيص ذاكرة جديدة ونسخ البيانات الفعلية، ما يضمن امتلاك كل كائن لموارده الخاصة.

challenge icon

التحدي

سهل

لنبنِ نظامًا لسجل الرسائل يوضّح أهمية النسخ العميق عندما يدير صنفك ذاكرة ديناميكية. ستنشئ صنفًا باسم MessageLog يخزّن الرسائل في مصفوفة مخصّصة ديناميكيًا، وتنفّذ مُنشئ نسخ مناسبًا لضمان امتلاك كل نسخة لذاكرتها المستقلة.

ستنشئ ملفين لتنظيم الشيفرة:

  • MessageLog.h: عرّف صنفًا باسم MessageLog يدير مجموعة من الرسائل باستخدام الذاكرة الديناميكية. يجب أن يحتوي صنفك على:
    • أعضاء خاصة: مؤشّر إلى مصفوفة سلاسل لتخزين الرسائل، وcapacity لتحديد الحد الأقصى لعدد الرسائل، وcount لتحديد العدد الحالي للرسائل
    • مُنشئ ذو معاملات يأخذ السعة، ويخصّص المصفوفة على الكومة، ويهيّئ العدد إلى 0
    • مُنشئ نسخ ينفّذ نسخًا عميقًا: يخصّص ذاكرة جديدة وينسخ جميع الرسائل من الكائن المصدر
    • مُدمّر يحرّر الذاكرة المخصّصة ويطبع "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، رسالة واحدة في كل سطر

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

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

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

ضمّن ملف الترويسة في main.cpp باستخدام #include "MessageLog.h".

جرّب بنفسك

#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' باستخدام مُنشئ النسخ

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

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

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

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

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

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C++ عبر الإنترنت