منشئ النسخ
جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 20 من 104.
يُنشئ منشئ النسخ كائنًا جديدًا كنسخة من كائن موجود. ويُستدعى عند تهيئة كائن من كائن آخر من النوع نفسه، أو تمرير كائن حسب القيمة، أو إرجاع كائن حسب القيمة.
يأخذ مُنشئ النسخ مرجعًا ثابتًا إلى كائن من الفئة نفسها:
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، ينشئ المصرّف واحدًا يُجري نسخًا سطحيًا - إذ ينسخ قيمة كل عضو مباشرةً. يعمل هذا بشكل جيد مع الأنواع البسيطة، لكنه يسبب مشكلات عندما تدير 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; }
};من دون منشئ copy مخصص، كان both الكائنين سيشيران إلى الذاكرة نفسها. عند تدمير أحدهما، كان لدى الآخر مؤشر متدلٍّ. تعمل نسخة عميقة على تخصيص ذاكرة جديدة ونسخ البيانات الفعلية، مما يضمن امتلاك كل كائن لموارده الخاصة.
التحدي
سهللنبنِ نظامًا لسجل الرسائل يوضّح أهمية النسخ العميق عندما تدير فئتك ذاكرة ديناميكية. ستنشئ فئة MessageLog تخزّن الرسائل في مصفوفة مخصّصة ديناميكيًا، وتنفّذ مُنشئ نسخ مناسبًا لضمان امتلاك كل نسخة لذاكرة مستقلة خاصة بها.
ستنشئ ملفين لتنظيم التعليمات البرمجية:
MessageLog.h: عرّف فئةMessageLogتدير مجموعة من الرسائل باستخدام الذاكرة الديناميكية. يجب أن تحتوي فئتك على:- أعضاء خاصة: مؤشر إلى مصفوفة من السلاسل النصية لتخزين الرسائل، و
capacityللعدد الأقصى من الرسائل، وcountللعدد الحالي من الرسائل - مُنشئ مُعلَّم بالمعاملات يأخذ سعة، ويخصّص المصفوفة على الكومة، ويهيّئ 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، رسالة واحدة في كل سطر
- أنشئ كائن
سيكون تنسيق الإدخال كما يلي:
- السطر الأول: السعة (عدد صحيح)
- السطر الثاني: الرسالة الأولى (سلسلة نصية)
- السطر الثالث: الرسالة الثانية (سلسلة نصية)
إذا نُفّذ النسخ العميق بشكل صحيح، فيجب أن يحتوي backup على رسالتين، بينما يحتوي original على 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' باستخدام copy constructor
// TODO: أضف "New message" إلى original فقط
// TODO: Print "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++ عبر الإنترنت