السلام عليكم ورحمة الله وبركاته
أخواني الأعزاء ساقوم بشرح ماهية الـ Copy constructor ( قد يكون هذا الموضوع قد شرح من قبل )
ولكن كما قيل من قبل "في الزيادة إفادة"
سيكون الشرح مقسم على النحو التالي :
* مراجعة سريعة للـ Constructor والـ Destructor
* ما هو الـ Dynamic Memory Allocation
* ماهو الـ Copy Constructor ومتى يستدعى
* كيف يمكن للـ Copy constructor أن يكون المنقذ
أولا: مراجعة سريعة للـ Constructor والـ Destructor
البناء والهدام أو الـ Constructor والـ Destructor هما دالتان خاصه يتم إستدعائهما بطريقة اوتوماتيكية وذلك لغرض إعطاء قيمة إبتدائية
( constructor ) أو لتحرير مصادر النظام ( Destructor )
دعونا نلقي نظرة على الكود التالي :
#include <iostream>
using namespace std;
class SomeClass {
public:
SomeClass () {
cout << "A constructor has been called!\n";
}
~SomeClass () {
cout << "A destructor has been called!\n";
}
};
int main(void) {
SomeClass object;
}ما حصل في الكود أعلاه إنه عند إنشاء كان من نوع SomeClass قام الـ Compiler بإستدعاء البناء وبعد الخروج من الـ Scope أي بعد الإنتهاء من دالة الـ main سيقوم الـ Compiler بتسريح الكائن ob عن طريق الهدام
ويمكن أن نقوم بإرسال بعض القيم للبناء وهذا ما يسمى بـ Parameterized Constructor والأمر يزداد جمالا حيث يمكنك تعريف أكثر من Constructor وهذا ما يسمى بـ Constructor Overloading ولكي نوضح الفكره أكثر دعونا نجري بعض التعديلات على الكود أعلاه كي يصبح بالشكل التالي :
#include <iostream>
using namespace std;
class SomeClass {
private:
int value;
public:
SomeClass () {
cout << "A constructor has been called!\n";
}
SomeClass(int v) {
cout << "A parameterized constructor has been called\n";
value = v;
}
~SomeClass () {
cout << "A destructor has been called!\n";
}
int get() { return value; }
};
int main(void) {
SomeClass object;
SomeClass object2(100);
cout << "Value in object2 is: " << object2.get() << endl;
}ما يحدث أعلاه لا يقتصر على الدالة main بل قد يحدث في داله يكون فيها تعريف لكائن من أي نوع كلاس يحوي هدام أو بناء، دعونا نجري بعض التعديلات في الكود اعلاه:
#include <iostream>
using namespace std;
class SomeClass {
private:
int value;
public:
SomeClass () {
cout << "A constructor has been called!\n";
}
SomeClass(int v) {
cout << "A parameterized constructor has been called\n";
value = v;
}
~SomeClass () {
cout << "A destructor has been called!\n";
}
int get() { return value; }
};
void run() {
cout << "Now we're inside function run\n";
SomeClass ob;
}
int main(void) {
SomeClass object;
SomeClass object2(100);
cout << "Value in object2 is: " << object2.get() << endl;
cout << "\n\n\n\n\n\n\n";
run();
cout << "Now we're back to main\n\n\n\n\n";
}ثانيا: ما هو الـ Dynamic Memory Allocation
بشكل مبسط الـ Dynamic Memory Allocation هو القدرة على حجز موقع أو مساحة من الذاكره لتخزين متغيرات سواء كانت بدائية ( int, float, double) أو كانت Arrays أو أي كائن من أي Class وذلك عن طريق المؤشرات، خذ الكود التالي :
#include <iostream>
using namespace std;
int main(void) {
int *intp = new int;
float *floatp = new float;
double *doublep = new double[10];
}أعتقد أن الفكرة واضحة، دعونا نكمل ونسأل ما علاقة هذا الكلام بكلا من البناء والهدام، سنبين العلاقة بالكود التالي:
#include <iostream>
using namespace std;
class myclass {
int *value;
public:
myclass(int v) {
value = new int;
*value = v;
}
~myclass() {
delete value;
}
int get() { return *value; }
};
int main(void) {
myclass ob(100);
cout << ob.get() << endl;
}إلى الآن لا يوجد شيء جديد أو مريب ولكن ماذا لو قمنا بتعريف دالة خارجية بإسم test وجعلناها تستقبل كائن من نوع myclass بحيث يتم إستدعاء الدالة get لهذا الكائن، التجربة خير برهان دعونا نرى
#include <iostream>
using namespace std;
class myclass {
int *value;
public:
myclass(int v) {
value = new int;
*value = v;
}
~myclass() {
delete value;
}
int get() { return *value; }
};
void test(myclass x) {
cout << x.get() << endl;
}
int main(void) {
myclass ob(100);
test(ob);
}اووووووووبسسسسسس، حدث خطأ غير متوقع، دعوني أقول لكم ماذا حدث بالضبط:
في الـ main قمنا بإنشاء كائن من نوع myclass تحت إسم ob حيث تم إستدعاء البناء وبعد ذلك قمنا بإرسال ob إلى الدالة test حيث هناك تم إنشاء نسخة ( copy ) من ob وتم تخزين بياناتها في x ( هنا تم إستدعاء copy constructor الإفتراضي ) وبعد الإنتهاء من الدالة test سيتم إستعدعاء الهدام الذي بدورة سيزيل المؤشر value الموجود في الكائن x، هنا ودعنا نكلم عن الـ copy constructor الإفتراضي
ماذا يفعل الـ copry constructor الإفتراضي ؟
سيقوم بكل بساطة بنسخ كل بيانات الكائن ob إلى الكائن x كما لو أننا كتبنا الكود التالي ( مع إهمال مراعاة الـ access modefier ):
x.value = ob.value
المصيبه هنا أن المتغير value هو من نوع pointer بمعنى آخر انه لم يتم نسخ قيمة بل نسخ عنوان ( Address ) والمصيبة الأعظم أن الهدام يقوم بمسح هذا المتغير، والهدام تم إستدعاءه مرتين ( مرة بعد الخروج من test ومرة بعد الخروج من main ) مما أدى إلى حذف مؤشر تم حذفة مسبقا ( بعد الخروج من الـ main ).
كيف يتم حل هذه المشكله ؟ بكل بساطة عن طريق إعادة تعريف الدالة test بحيث تقوم بإستقبال reference بدل من نسخه على النحو التالي:
void test(myclass& x) {
cout << x.get() << endl;
}
الحل السابق هو حل مؤقت ولا يخدم إلا الحالة السابقة أعلاه، إذا ماهو الحل الأمثل؟
ثالثا: ماهو الـ Copy Constructor ومتى يستدعى
كما رأينا في القسم الثاني ( العيد الممكن أن يحدث بسبب الـ Default Copy Constructor ) وايضا الـ reference ليس بالحل الكافي الشافي سنرى كيف يتم معالجة المشكلة التي وردت أعلاه ولكن قبل ذالك دعونا نجاوب عل السؤال : متى يستدعى الـ copy constructor ؟
يستدعى في حالة عامة وهي عندما يقوم كائن بإبداء (initialize) كائن آخر في جملة التعريف كما هو مبين في الكود التالي :
SomeClass object = already_existing_object; \\ copy constructor is called SomeClass object2; object2 = already_existing_object; \\ this won't call the copy constructor
ما حدث معنا في الدالة test التي وردت في القسم الثاني أعلاه هو نفس الشيء، حيث تم إستخدام الكائن ob لإنشاء الكائن x في جملة التعريف.
الشكل العام للـ copy construcot :
class_name(const class_name& ob) {
// deffinetion gose here
}رابعا: كيف يمكن للـ Copy constructor أن يكون المنقذ
عن طريق إعادة إتمام عملية النسخ بما يتوافق مع كيفية تعريف الـ Class ( في وضعنا الحالي عن طريق نسخ القيمة بدل العنوان )
كما حدث معنا قام الـ Default copy constructor بنسخ كل متغير من الكائن ob إلى مايقابله في الكائن x وهذا ما يسمى بالنسخ السطحي او الضحل shallow copying ولكن نحن سنقوم بإعادة تعريف الـ copy constructor يقوم بنسخ القيمة وليس العنوان وهذا ما يسمى بالنسخ العميق deep copying وسيكون الـكود بالشكل التالي:
#include <iostream>
using namespace std;
class myclass {
int *value;
public:
myclass(int v) {
value = new int;
*value = v;
}
myclass (const myclass& obj) {
value = new int;
*value = *obj.value;
}
~myclass() {
delete value;
}
int get() { return *value; }
};
void test(myclass x) {
cout << x.get() << endl;
}
int main(void) {
myclass ob(100);
test(ob);
}لو تلاحظوا في الـ copry constructor أرسلنا إليها obj وهي التي سنقوم بأخذ قييمها التي سيتم نسخها ثم قمنا بحجز موقع جديد للمتغير value بدلا من من نسخ العنوان فقط
ملاحظه: هذا ايضا ليس الحل الكافي، دعونا ننظر في الكود التالي الذي سبق وورد معنا في القسم الثالث
SomeClass object2; object2 = already_existing_object; \\ this won't call the copy constructor
هنا لن يفيدنا الـ copy constructor وسنعود لنفس الدوامه، ترى ماذا سيكون الحل ؟؟؟؟؟؟ سأقوم بإعطائكم الحل في المرة المقبله إنشاء الله.
تلميح : operator overloading
أسأل الله أن يجعل ذلك في ميزان حسناتنا وأتنى من الله العلي الجليل أن يكتب التوفيق لي ولكم ولسائر المسلمين
مع تحيات أخوكم Robatic

