منشئ النسخ
جزء من قسم البرمجة كائنية التوجه في رحلة 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; }
};من دون مُنشئ نسخ مخصّص، سيشير الكائنان إلى الذاكرة نفسها. وعندما يُدمَّر أحدهما، سيصبح لدى الآخر مؤشر معلّق. تقوم النسخة العميقة بتخصيص ذاكرة جديدة ونسخ البيانات الفعلية، ما يضمن امتلاك كل كائن لموارده الخاصة.
التحدي
سهللنبنِ نظامًا لسجل الرسائل يوضّح أهمية النسخ العميق عندما يدير صنفك ذاكرة ديناميكية. ستنشئ صنفًا باسم 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;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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مراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت