Robatic

ماهية ال Copy Constructor

13 ردود في هذا الموضوع

السلام عليكم ورحمة الله وبركاته

أخواني الأعزاء ساقوم بشرح ماهية الـ 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

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

جزاك الله خير االجزاء

شرح وافي وجميل عن هذا الموضوع ..

object2 = already_existing_object;

يجب عليك اعادة تعريف ( =) او ما يسمى بال overloading . لينقل المعلومات من الطرف الايمن الى الايسر ..

لن اتكلم اكثر لادعك تكمل شرحك ....

تحياتي العطرة ..

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

السلام عليكم ,

بارك الله فيك أخي العزيز, تأتي بالمفيد و الجديد دائماً,

الدوال التي عرضتها تسمى أحياناً بالدوال القياس عند تصميم الأصناف classes, لأن العادة لدي مبرمجي ++C أن يتم تعريف تلك الدوال في أي صنف كان بغض النظر عمله!

تحياتي ,,

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

السلام عليكم ورحمة الله وبركاته

دعونا نكمل، لقد وقفنا عند مشكلة:

SomeClass object;
object = already_existing_object;

وقلنا إنه الـ copy constructor لن يفيد هنا، ما الذي سوف يفيدنا ؟؟؟؟ كل ما علينا فعله هو إعادة تعريف العملية =

وهو ما يسمى بالـ operator overloading، ولكن قبل ذلك دعونا نلقي نظرة للـ Class الموجود لدينا وهو:

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

نظرا لطبيعته ( إحتواءة على مؤشر ) يجب مراعاة ما يلي قبل إعادة تعريف العملية =

1) أولا التأكد من الطرف الأيمن من العملية مع الطرف الأيسر حيث أنهم قد يكونوا نفس الكائن، فإذا قمنا بحذف المؤشر لأي منهما سيؤدي ذلك لفقدان البيانات بحكم أنهم يؤشرون على نفس العنوان.

2) حذف المؤشر الحالي للطرف الأيسر من العملية وحجز عنوان جديد له.

3) نقل قيمة المؤشر للطرف الأيمن من العملية إلى الطرف الأيسر.

وإليكم الـ code:

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

myclass operator = (myclass& left_operand) {

if(this == &left_operand)
return *this;

delete value;

value = new int;

*value = *left_operand.value;

return *this;
}
};

void test(myclass x) {
cout << x.get() << endl;
}

int main(void) {
myclass ob(100), ob2(10);

ob2 = ob;

cout << "Value is: " << ob2.get() << endl;
}

لاحظوا تعريف العملية = لقد تم إرسال parameter واحد فقط وهو يمثل الجزء الأيمن من العملية حيث أن الجزء الأيسر سيتم إرساله ضمنيا عن طريق المؤشر this الذي يؤشر على نفس الكائن الذي قام بإستدعاء الـ operator ويمكن القيام بذلك عن طريق تعريف friend function حيث يتم إرسال 2 parameters إلى الـ operator= وهما الجزء الأيسر والجزء الأيمن ولكن ليس موضوعنا.

هكذا إنتهينا من الـ copy constructor والمشاكل التي تظهر عندما نستخدم الـ Dynamic Memory Allocation في الـ Classes

أسأل الله العلي الجليل أن يرزقني وإياكم بالعلم والحكمه وان يجعل مجلسنا هذا رحمة لنا، وأستودعكم الله إلى أن ألقاكم في درس آخر

أخوكم Robatic

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

أهلا أخي روبتوك ، موضوع ممتاز ولي تعليق حول النقطه الأخيره ، وهي موضوع اعاده تعريف Copy Assigment Operator .

فالكود الذي كتبته أخي صحيح (وكتبته أيضا أنا في موضوع سابق ) ، ولكنه في يعتبر في بعض الحالات وخاصه عند استخدام برنامج يتعامل مع الـ Exception فإنه ربما ينتج عنه مشاكل ما exception-unsafe .

وحل هذه مشكله اعاده تعريف Assigment Operator تكون بثلاث طرق :

1- ذكرتها أنت في موضوعك وهي أختبار عنوان الكائن الأول مع الكائن الحالي ، فاذا تساوى فمعني ذلك أنهم متشابهين .

2- عن طريق أخذ قيمه المؤشر الحالي قبل حذفه

3- عن طريق النسخ والتغيير Copy And Swap .

نأخذ مثال بسيط يوضح هذه الطرق .....

لدينا كلاسين :

الأول يمثل صوره ما Bitmap .

والأخر Widget يحتوي على مؤشر لهذه الصوره ... جميل .

class Bitmap { ... };

الثاني :

class Widget {

...



private:

Bitmap *pb; // ptr to a heap-allocated object

};

الأن هذه الحاله الأولى ، وهي أختبار عنوان الكائن الأول مع الكائن الحالي ، فاذا تساوى فمعني ذلك أنهم متشابهين ، ولكنها في بعض الأحيان تكون غير مناسبه .

Widget& Widget::operator=(const Widget& rhs)

{

if (this == &rhs) return *this; // identity test: if a self-assignment do nothing,

delete pb;

pb = new Bitmap(*rhs.pb);

return *this;

}

حسنا ، متى تكون الحاله الأولى غير مناسبه ؟

عندما تحصل مشكله في أعطاء قيمه ما للمؤشر pb، مثلا في حال فشلت عمليه حجز الذاكره ؟ مثلا حجم الذاكره غير كافيه أو قام مثلا Copy Constructor بعمل Throws للأستثناء ـ تذكر نحن نستخدم مفهوم الException Handling هنا .

المهم سوف يكون مؤشرنا يؤشر الى بيانات محذوفه وهو أمر خطير طبعا .

لذلك نلجأ للحل الثاني وهو exception-safe ومناسب أيضا في self-assignment-safe يعني يحل جميع المشاكل :) .

وطريقه الحل هي أن نأخذ قيمه الحاليه للمؤشر pb من قبل الحذف في مؤشر أخر ، بعدها نسند لها قيمه جديده ، ومن ثم نحذف المؤشر الجديد ، والمثال التالي يوضح ما حدث :

Widget& Widget::operator=(const Widget& rhs)

{

Bitmap *pOrig = pb; // remember original pb

pb = new Bitmap(*rhs.pb); // make pb point to a copy of *pb

delete pOrig; // delete the original pb

return *this;

}

الان اذا حدثت نفس المشكله السابقه فن المتغير pb يبقى كما هو بدون تغيير ، أيضا هذا الكود يحل مشكله اسناد الكائن لنفسه self-assignment-safe حيث نقوم بعمل نسخه من الصوره الحاليه ، وبعدها نعطيها قيمه جديده ، ومن ثم نحذف النسخه .

هي طريقه ليست efficiency بشكل كامل ، ولكنها تعمل .

ويمكن أن تضع جمله اختبار الكائن الحالي بالمرسل identity test في هذه بدايه الطريقه وسوف تكون أكثر كفائه .

الطريقه الثالثه تعرف بـ copy and swap ، وهي جيده وينصح بها البعض ، حيث تقوم على مبدأ Swap ولكن من جهه واحده ، ستفهم من المثال التالي :

نقوم أولا باضافه داله Swap في widget :

class Widget {

...

void swap(Widget& rhs); // exchange *this's and rhs's data;

...

};

الان الداله ، سوف تستقبل الكائن المرسل بالقيمه ، وليس بالمرجع ، لأننا سوف نعمل Swap بين القيمه rhs المرسله مع الكائن الحالي ، ولا نريد أن نغير من الكائن المرسل ، فقط نأخذ القيمه ...

Widget& Widget::operator=(Widget rhs)   // rhs is a copy of the object

{ // passed in — note pass by val



swap(rhs); // swap *this's data with

// the copy's



return *this;

}

أرجوا أن تكون المشاركه واضحه ،

Make sure operator= is well-behaved when an object is assigned to itself. Techniques include comparing addresses of source and target objects, careful statement ordering, and copy-and-swap

Form : Effictive c++ , One of the Best Book in Tha Language :)

تم تعديل بواسطه romansy
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

السلام عليكم ,,

أخي romansy أعترف أنك من ضمن القلائل الذين أعجب بمشاركاتهم,

يبدو أنك تقرأ الكثير لـ Scott Meyers B)

قال لي أحد الزملاء الأمريكان حرفياً,

you are not a real c++ programmer until your read Scott Meyers books!

و الحقيقة أتفق معه في هذه النقطة, منذ مدة و أنا أقرأ مقالاته على النت, و كتبه لدي على الجهاز و لكني أتصفحها وأختار ما أريده منها,

سأحاول وضع الكتب في مكتبة القسم,

تحياتي ,,

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

فعلا أخي خالد ، هذا المبرمج ممتاز جدا ، ودائما ما ينصح الخبراء بكتبه ، فهي لا تشرح أساسيات اللغه فقط ، ولكنها تتناول النقاط الغامضه وتشرحها بشكل رائع جدا ، وليس كما في أغلب الكتب .

هناك أيضا كتب المبرمج Jesse Liberty مثل :

Teach Yourself C++ in 21 Days

C++ Unleashed

وهي من الكتب القويه في اللغه ، والكاتب مبرمج محترف ومهندس برمجيات متمرس جدا ....

شاكر لك مرورك وثنائك الطيب :) .

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

بمناسبة حديثكم عن Scott Meyers ..

هو بالفعل من المبرمجين المشهورين على مستوى العالم بالسي++ .

وانصح كل شخص يريد تعلم هذه اللغه الدخول الى موقعه وهو على هذا الرابط ..

http://www.aristeia.com/

تحياتي العطرة ..

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

السلام عليكم

اخوي ممكن تشرح لي هذا الكود لما يكون عندنا مصفوفه نخزن فيها ثلاث عناصر

وكيف تكون عملية الطباعه لما تكون عندنا فنكشن للطباعه

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

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

السلام عليكم

اخوي ممكن تشرح لي هذا الكود لما يكون عندنا مصفوفه نخزن فيها ثلاث عناصر

وكيف تكون عملية الطباعه لما تكون عندنا فنكشن للطباعه

#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);

}

تم تعديل بواسطه emo147
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

جزاك الله خيرا

استفدت و الحمد لله من هذا الدرس

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

السلام عليكم.

درس مفيد أخي .

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

السلام عليكم ,,

أخي romansy أعترف أنك من ضمن القلائل الذين أعجب بمشاركاتهم,

يبدو أنك تقرأ الكثير لـ Scott Meyers B)

قال لي أحد الزملاء الأمريكان حرفياً,

you are not a real c++ programmer until your read Scott Meyers books!

و الحقيقة أتفق معه في هذه النقطة, منذ مدة و أنا أقرأ مقالاته على النت, و كتبه لدي على الجهاز و لكني أتصفحها وأختار ما أريده منها,

سأحاول وضع الكتب في مكتبة القسم,

تحياتي ,,

نعم كتبه مميزة. لكن من وجهة نظري بالنسبة للمبتدئين انصح بكتاب C++ Primer فعلا كتاب رائع جدا يشرح تقريبا معظم الامور في C++ . ثم كتاب the C++ programming language لمؤسس لغة C++ . و كتب اخرى مثل the Art of C++ .

لكن افضل شيئ للتعلم هو التطبيق .. لن تتعلم C++ الا بالتطبيق.

+

لدي مكتبة هائلة و ضخمة جدا جدا جدا من كتب C++ و C بمختلف انواعها و مجالاتها فيها كتب للمبتدئين و المحترفين(تقريبا 1جيغابايت).سانتقي منها اهم الكتب و اضعها في مكتبة المنتدى باذن الله.

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .