الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

تعرف على Virtual Function ...

مغلق
بدأه سنان محمد صالح في 7 فبراير 2008 · 11 رد · 8,075 مشاهدة · في المواضيع والدروس
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

بسم الله الرحمن الرحيم

اهلا اخواني واخواتي اعضاء الفريق العربي للبرمجة ..

اليوم لدينا موضوع مهم من مواضيع البرمجة الكائنية .. وهو الVirtual Function ..

لنتخيل اننا نبرمج اشكال صورية (graphices) .. تحتوي على اشكال ؟(مربعات او دوائر ) وخطوط او اي شكل .. وهذه الاشكال نريد طباعتها على الشاشه . وبالطبع كل هذه الاشكال تعتمد على X,Y احداثيات الشاشه للطباعه .. فكيف سيتم هذا ؟؟

في لغة السي ++ .. نستطيع فعل ذلك بطريقة واحدة في البرمجة الكائنية وهو مايسمى بال virtual function .

اذا .. ماهي الvirtual function .. ؟؟؟

هي عبارة عن دالة عضو في صنف (class ) ... تعرف باستخدام الكلمة المحجوزة virtual .. تمكننا هذه الدوال من تغيرها والتعديل عليها بالاصناف المورثة من الصنف العام الذي يحتويها ..

ويمكن ايضا ان يعرف مؤشر من نوع هذا الصنف الذي يحتوي على الدالة الظاهرية او virtual function . وبعدها يتم استدعاءها عن طريق هذا المؤشر باستخدام ( <- ) .

تعريف ويكيبيديا لها ..

اقتباس
In object-oriented programming (OOP), a virtual function or virtual method is a function whose behavior, by virtue of being declared "virtual," is determined by the definition of a function with the same signature furthest in the inheritance lineage of the instantiated object on which it is called. This concept is a very important part of the polymorphism portion of object-oriented programming (OOP).

سانقل لكم الكود الموجود في ويكيبيديا لانه سهل وواضح واشرحه .. وبعدها اكتب لكم مثال اخر لتوضيحها ..

#include <iostream>
using namespace std;

class Animal
{
public:
   virtual void eat() { cout << "I eat like a generic Animal." << endl; }
};

class Wolf : public Animal
{
public:
   void eat() { cout << "I eat like a wolf!" << endl; }
};

class Fish : public Animal
{
public:
   void eat() { cout << "I eat like a fish!" << endl; }
};

class OtherAnimal : public Animal
{
};

int main()
{
   Animal* anAnimal[4];

   anAnimal[0] = new Animal();
   anAnimal[1] = new Wolf();
   anAnimal[2] = new Fish();
   anAnimal[3] = new OtherAnimal();

   for (int i = 0; i < 4; i++) {
	  anAnimal->eat();
   }

   for (int i = 0; i < 4; i++) {
	  delete anAnimal;
   }

   return 0;
}

في البداية بدأنا بانشاء صنف جديد وهو صنف (animal) . وتم التعريف في الجزء العام من هذا الصنف الدالة الظاهرية وهي دالة الاكل الخاصة بكل الحيوانات . ( السطر التالي ) ..

public:
   virtual void eat() { cout << "I eat like a generic Animal." << endl; }

بعدها تمت برمجة اصناف اخرى مورثة من هذا الصنف العام ... والاصناف الجديدة هي صنف الذئب والسمكه وحيوانات اخرى .. (ظهرت في السطور التالية ) ,,,

class Wolf : public Animal
class Fish : public Animal
class OtherAnimal : public Animal

نشاهد في السطور الثلاثة اعلاه كيف تمت عملية الوراثة من الصنف العام animal ..

عن طريق الوراثة ..ورثت الاصناف الجديده دالة الدالة الظاهرية من الصنف العام animal ..

لنلاحظ ما هي فائدة الدوال الظاهرية في المثال اعلاه ..

عند وراثة الصنف الجديد wolf لدوال الصنف العام animal فقد اخذ منه الدالة الظاهرية eat() والمعرفة بالشكل التالي (داخل animal)

  virtual void eat() { cout << "I eat like a generic Animal." << endl; }

حيث تم استخدام الكلمة المحجوزة virtual قبلها .. ولكن نفس الدالة الان موجوده في wolf وايضا void .. ولكنها تطبع جملة اخرى حيث انها معرفه بالشكل التالي ..

void eat() { cout << "I eat like a wolf!" << endl; }

اما في داخل الصنف fish او السمكة .. فهي ايضا ورثت الدالة الظاهرية من الصنف العام animal ولكن تطبع جملة اخرى (كما حدث في الصنف السابق wolf ) فدالة eat في صنف السمكة معرفة بالشكل التالي ..

void eat() { cout << "I eat like a fish!" << endl; }

وبالنسبه للصنف (بقية الحيوانات other animal) فانه ايضا يرث دالة eat من الصنف العام ولكنه لا يحتوي على تعريف جديد خاص بهذه الدالة ..

لذلك فهو سيطبع نفس ما موجود في الصنف العام animal ..

لذلك فان اخراج الكود السابق على الشاشة سيكون بالشكل التالي ..

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
I eat like a generic Animal.

نلاحظ ان او سطر طبع ماموجود في الصنف العام .. اما السطر الثاني فانه طبع ماموجود في الصنف الثاني wolf .. على الرغم من وراثته للدالة من الصنف العام .. الا انه طبع شئ اخر خاص به . وسبب حدوث ذلك هو استخدام الكلمة المحجوزة virtual في الصنف العام والذي جعل الدالة eat ظاهرة لكل الاصناف التي ترثها ويمكن التعديل بها او تركها كما هي ..

لكن .. لو كان كود البرنامج السابق بنفس الشكل كله .. ولكن لم نستخدم الكلمة المحجوزة virtual قبل الدالة eat في الصنف العام ... فماذا سيكون الاخراج ... ؟؟

بكل بساطه تطبع جملة الصنف العام لاربع مرات كما هي من دون اي تغيير يطرا عليها من الصنفين الاخرين wolf و fish والسبب في ذلك انها ورثت دالة لايمكن التغيير بها او تعديلها ..

فيكون ناتج الطبع على الشاشه ..

I eat like a generic Animal.
I eat like a generic Animal.
I eat like a generic Animal.
I eat like a generic Animal.

وهذه الجملة هي الموجوده في الصنف العام ..

++==++==++==++==++==++==++==

لنأخذ مثال اخر .. وبه سنرى المؤشر مع الدوال الظاهرية ..

#include <iostream.h>
class base
{
public:
virtual void display()
{
  cout<<”\nBase”;
}
};
class derived : public base
{
  public:
  void display()
  {
	 cout<<”\nDerived”;
  }
};

void main()
{

  base *ptr = new derived();
  ptr->display();
}

تم تعريف صنف جديد تحت اسم base الجزء العام منه يحتوي على الدالة الظاهريةdisply والمعرفة بالشكل التالي ..

virtual void display()
{
  cout<<”\nBase”;
}

بعدها برمجنا صنف جديد باسم derived وهو الصنف الابن الذي سيرث الصنف العام base .

فقد ورث منه الدالة الظاهرية disply ولكنه عرفها بشكل اخر .

  void display()
  {
	 cout<<”\nDerived”;
  }

حيث انه سيطبع كلمة تختلف عما سيطبعه بالصنف العام base .

نأتي الان الى الدالة main وما سيحدث في داخلها ..

void main()
{

  base *ptr = new derived();
  ptr->display();
}

تم تعريف مؤشر من نوع الصنف العام base وهو يشير الى جزء جديد من الذاكرة من نوع derived الذي ورث صفاته من base .

الاخراج الذي سيكون على الشاشة من استخدام الجملة .

ptr->display();

هو ما تحتوي دالة disply في الصنف الابن derived . والسبب في ذلك هو ان هذه الدالة هي دالة virtual اي انه يسمح بتغيرها في الدوال الابناء . وايضا ان المؤشر هو من نوع base ولكنه يشير الى بيانات من نوع derived فلذلك الدالة التي ستنفذ هي التي في الصنف الابن derived .

ولكن .. لو لم نستخدم الكلمة المحجوزة virtual مع هذه الدالة (اي اننا لم نجعلها ظاهريه ) مع نفس الكود السابق .. فما هو الاخراج ؟؟

الاخراج سيكون طباعة واستخدام الدالة الموجودة في الصنف العام base لانه ورث دالته الى الصنف الابن derived وجعله مجبرا على تنفيذ ما بداخلها من دون تغيير .

لدينا الان جزء مهم جدا يجب عليكم معرفته وهو ..

دالة ال Constructor او الدالة البانية للصنف (كما تترجم ) لا يمكن ان تكون دالة ظاهرية ابدا ابدا ..

والسبب في ذلك هو انه عند تنفيذ او بداية عمل دالة constructor الخاصه بالصنف فان الvirtual function تكون غير مسموحة في تلك الاثناء . لذلك فانه لايمكن ان نجعل الconstructor دالة virtual function .

اي ان الكود التالي خاطئ خاطئ ..

class base {
virtual base ();
};

مثال ..

#include <iostream.h>
class base
{
public:
virtual ~base()
{

}
};

class derived : public base
{
   public:
   ~derived()
   {

   }

};

void main()
{

   base *ptr = new derived();
   // some code
   delete ptr;
}

كود قريب من الكود السابق مع تغيرات بسيطة به يوضح الفقرة الاخيرة ..

ارجو ان يكون الموضوع مفيد لكم ..

واي اضافات او ملاحظات او تعقيبات على الموضوع ارجو ان تذكورها .

واذا كانت هناك فقرة غير واضحه انا في الخدمة ..

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

يَارَبُ إِن ضَاقَت قُلُوُب الْنَّاسٍ عَنْ مّافِي .. مِنْ خَيْرٍٍ فَعَفْوكَ لَا يَضِيْقْ ..

#2

هذا مثال طويل ومعقد وجدته لدي .. وهو عن الاشكال الهندسيه ..

يوضح ايضا الvirtual function ... وطرق الوراثة وعمل الدوال الظاهريه ...

#include <string.h>
		#include <assert.h>
		#include <iostream.h>

		typedef double Coord;

		/*
		The type of X/Y points on the screen.
		*/

		enum Color {Co_red, Co_green, Co_blue};

		/*
		Colors.
		*/

		// abstract base class for all shape types
		class Shape {
		protected:
				Coord xorig; // X origin
				Coord yorig; // Y origin
				Color co; // color

		/*
		These are protected so that they can be accessed
		by derived classes.  Private wouldn't allow this.

		These data members are common to all shape types.
		*/

		public:
				Shape(Coord x, Coord y, Color c) :
					xorig(x), yorig(y), co(c) {} // constructor

		/*
		Constructor to initialize data members common to
		all shape types.
		*/

				virtual ~Shape() {} // virtual destructor

		/*
		Destructor for Shape.  It's a virtual function.
		Destructors in derived classes are virtual also
		because this one is declared so.
		*/

				virtual void draw() = 0; // pure virtual draw() function

		/*
		Similarly for the draw() function.  It's a pure virtual and
		is not called directly.
		*/

		};

		// line with X,Y destination
		class Line : public Shape {

		/*
		Line is derived from Shape, and picks up its
		data members.
		*/

				Coord xdest; // X destination
				Coord ydest; // Y destination

		/*
		Additional data members needed only for Lines.
		*/

		public:
				Line(Coord x, Coord y, Color c, Coord xd, Coord yd) :
					xdest(xd), ydest(yd),
					Shape(x, y, c) {} // constructor with base initialization

		/*
		Construct a Line, calling the Shape constructor as well
		to initialize data members of the base class.
		*/

				~Line() {cout << "~Line\n";} // virtual destructor

		/*
		Destructor.
		*/

				void draw() // virtual draw function
				{
						cout << "Line" << "(";
						cout << xorig << ", " << yorig << ", " << int(co);
						cout << ", " << xdest << ", " << ydest;
						cout << ")\n";
				}

		/*
		Draw a line.
		*/

		};

		// circle with radius
		class Circle : public Shape {
				Coord rad; // radius of circle

		/*
		Radius of circle.
		*/

		public:
				Circle(Coord x, Coord y, Color c, Coord r) : rad(r),
					Shape(x, y, c) {} // constructor with base initialization

				~Circle() {cout << "~Circle\n";} // virtual destructor
				void draw() // virtual draw function
				{
						cout << "Circle" << "(";
						cout << xorig << ", " << yorig << ", " << int(co);
						cout << ", " << rad;
						cout << ")\n";
				}
		};

		// text with characters given
		class Text : public Shape {
				char* str; // copy of string
		public:
				Text(Coord x, Coord y, Color c, const char* s) :
					Shape(x, y, c) // constructor with base initialization
				{
						str = new char[strlen(s) + 1];
						assert(str);
						strcpy(str, s);

		/*
		Copy out text string.  Note that this would be done differently
		if we were taking advantage of some newer C++ features like
		exceptions and strings.
		*/

				}
				~Text() {delete [] str; cout << "~Text\n";} // virtual dtor

		/*
		Destructor; delete text string.
		*/

				void draw() // virtual draw function
				{
						cout << "Text" << "(";
						cout << xorig << ", " << yorig << ", " << int(co);
						cout << ", " << str;
						cout << ")\n";
				}
		};

		int main()
		{
				const int N = 5;
				int i;
				Shape* sptrs[N];

		/*
		Pointer to vector of Shape* pointers.  Pointers to classes
		derived from Shape can be assigned to Shape* pointers.
		*/

				// initialize set of Shape object pointers

				sptrs[0] = new Line(0.1, 0.1, Co_blue, 0.4, 0.5);
				sptrs[1] = new Line(0.3, 0.2, Co_red, 0.9, 0.75);
				sptrs[2] = new Circle(0.5, 0.5, Co_green, 0.3);
				sptrs[3] = new Text(0.7, 0.4, Co_blue, "Howdy!");
				sptrs[4] = new Circle(0.3, 0.3, Co_red, 0.1);

		/*
		Create some shape objects.
		*/

				// draw set of shape objects

				for (i = 0; i < N; i++)
						sptrs->draw();

		/*
		Draw them using virtual functions to pick up the
		right draw() function based on the actual object
		type being pointed at.
		*/

				// cleanup

				for (i = 0; i < N; i++)
						delete sptrs;		

		/*
		Clean up the objects using virtual destructors.
		*/

				return 0;
		}

الاخراج على الشاشه سيكون ..

Line(0.1, 0.1, 2, 0.4, 0.5)
		Line(0.3, 0.2, 0, 0.9, 0.75)
		Circle(0.5, 0.5, 1, 0.3)
		Text(0.7, 0.4, 2, Howdy!)
		Circle(0.3, 0.3, 0, 0.1)
		~Line
		~Line
		~Circle
		~Text
		~Circle

اذا لم يكن المثال واضحا لكم .. اوضحه ..ولكن اراه بسيطا ولايستوجب شرح ..

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

يَارَبُ إِن ضَاقَت قُلُوُب الْنَّاسٍ عَنْ مّافِي .. مِنْ خَيْرٍٍ فَعَفْوكَ لَا يَضِيْقْ ..

#3

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

شكراً أخ سنان على الدرس الرائع,

بما أنك جئت على ذكر الـ Virtual Functions, أحب أن أذكر ميزة رائعة في JAVA :rolleyes:

في JAVA, كل كائن يجب أن يرث الكائن الأم المسمى Object هذا الكائن يعرف دالة Virtual اسمها toString,

بحيث أننا يمكننا استدعائها من أي كائن لتحويل بياناته إلى نصوص, دون معرفة نوع الكائن!

جميل جداً اليس كذلك,

للأسف ++C, لا تعترف بما يسمى الكائن الأم في تطبيقها لمبدأ البرمجة الكائنية,

تحياتي ,,

#4

شكرا خالد على المرور العطر ..

اقتباس
للأسف ++C, لا تعترف بما يسمى الكائن الأم في تطبيقها لمبدأ البرمجة الكائنية

نعم هذه الميزة تفتقر لها السي ++ ..

وانا اردت ان اكمل الموضوع وهو عن الدوال الظاهرية بالنسبه للغة الجافا ... ولكن توقفت لكي اتركها لقسم الجافا .. فهنا لدينا السي ++ .

بالمناسبه .. ماهو رايكم بترجمتي لها بالدوال الظاهرية ..؟؟

وين رغد عني الان ؟؟

يَارَبُ إِن ضَاقَت قُلُوُب الْنَّاسٍ عَنْ مّافِي .. مِنْ خَيْرٍٍ فَعَفْوكَ لَا يَضِيْقْ ..

#5
اقتباس
بالمناسبه .. ماهو رايكم بترجمتي لها بالدوال الظاهرية ..؟؟

ألم نتكلم عنها في موضوع توحيد المصطلحات وقلنا دوال افتراضية

من أقوال الأئمة

الإمام علي بن أبي طالب -رضي الله عنه-: "الناس ثلاثة : فعالم رباني ، ومتعلم على سبيل النجاة ، وهمج رِعاع غوغاء أتباع كل ناعق ، يميلون مع كل ريح ، لم يستضيئوا بنور العلم ، ولم يلجئوا إلى ركن وثيق".

الإمام مالك -رحمه الله- : " لن يصلح آخر هذه الأمة إلا بما أصلح به أولها ".

الإمام الأوزاعي -رحمه الله- : " عليك بآثار من سلف وإن رفضك الناس وإياك وآراء الرجال وإن زخرفوه لك بالقول ".

الإمام الفضيل بن عياض -رحمه الله- : " عليك بطرق الهدى و لا يضرك قلة السالكين و إياك و طرق الضلالة ولا يغرك كثرة الهالكين ".

علامة الزمان الإمام الألباني -رحمه الله- : " إن الخلاص إلى أيدي هؤلاء الشباب يتمثل في أمرين لا ثالث لهما ؛ التصفية والتربية ".

وكل خير في اتباع من سلف وكل شر في ابتداع من خلف

#6

طيب يا باسم ..

رايك انه اغير كل كلمة ظاهرية ب (افتراضيه ) ؟؟

اذا كان نعم لكي اغيرها ..

يَارَبُ إِن ضَاقَت قُلُوُب الْنَّاسٍ عَنْ مّافِي .. مِنْ خَيْرٍٍ فَعَفْوكَ لَا يَضِيْقْ ..

#7

بارك الله فيك ,, موضوع مهم , ولدي تعليق + سؤال .

- هناك ما يسمى بالinterface وهي فئة مجردة تماما .. جميع دوالها تكون بهذه الصيغة

class A
{
virtual void f1()=0;
virtual void f2()=0;
}

ونلاحظ اننا لانضيف data members ولا constructor .

بالجافا نستطيع عمل السابق بكلمة واحدة هي interface .

وأيضا هناك الabtract class ...

- لدي سؤال :

دائما ما نرى المهدم Destructor يكون virtual ... ماهو السبب ... قرأت كثيرا ولكن لم أفهم :D .

تم تعديل هذه المشاركة بواسطة الشمري في 8 فبراير 2008 في 17:16

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#8

موضوع مميز كعادتك أخي سنان .

اقتباس
دائما ما نرى المهدم Destructor يكون virtual ... ماهو السبب ... قرأت كثيرا ولكن لم أفهم

حتى يتم حذف الكائن بشكل كامل ،

مثلا في حاله لم أضع virtual في الكلاس الأب في الداله Destructor ، وقمت في البرنامج الرئيسي بعمل مؤشر من الكلاس الأب يشيير الى الأبن

ClassA* ptr = new ClassB();

هنا عملنا كائن في Heap .. هذا الكائن استدعئ دالتين بناء الأولى الخاصه بالكلاس الأب ClassA والثانيه الخاصه بالكلاس الإبن ClassB (يمكن أن تتخيل هذه العمليه بأن الكائن من جزئين جزء للكلاس A والجزء الأخر لB ) .

delete ptr ;

الان هنا بعد الحذف سوف ينحذف الجزء الخاص بالكلاس الأب وسيبقى الجزء الخاص بالكلاس الإبن في الذاكره .لذلك وجب ان اضع الكلمه virtual والتي تستدعي أولا داله الهدم في B بعدها يتم حذف الجزء A .

والقاعده دائما :

اذا كانت هناك أي داله virtual في الكلاس ، اذا داله الهدم سوف تكون أيضا virtual .

أيضا قاعده أخرى ، لكن قلما من كان بحاجه اليها ، ويكمن أن نتناقش حولها :

داله البناء يجب أن لا تستدعي أيه داله virtual ...

من يثبت هذه القاعده :) .

تم تعديل هذه المشاركة بواسطة romansy في 8 فبراير 2008 في 18:25

http://informatic-ar.com منصة تعليمية عربية في علوم الحاسب والبرمجة

https://moalfat.com  للكتب الالكترونية والكورسات التعليمية

Everything we see now is just an engineering solution based on old science

#9

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

جميل يا رومانسي B)

اقتباس
داله البناء يجب أن لا تستدعي أيه داله virtual ...

لأن الكائن لم يكن يعرف أصلاً أن هناك VTable !

عملية الحصول على مؤشر الـ VTable المطلوب تتم خلال إنشاء الكائن. أليس كذلك ؟

#10

اشكركم يا اخوان على المرور والتقييم ..

نورتوا الموضوع ..

=====================

جميلة منكم المناقشه .. وانا اعتذر واعتبره من سوء حظي اني لم اكن موجودا اثناء هذه المناقشة الجميلة ..

اخي الشمري ...

جوابي على سؤالك موجود في الموضوع .. وساعيد لك الفقرة الان من دون الكود ..

اقتباس
لدينا الان جزء مهم جدا يجب عليكم معرفته وهو ..

دالة ال Constructor او الدالة البانية للصنف (كما تترجم ) لا يمكن ان تكون دالة ظاهرية ابدا ابدا ..

والسبب في ذلك هو انه عند تنفيذ او بداية عمل دالة constructor الخاصه بالصنف فان الvirtual function تكون غير مسموحة في تلك الاثناء . لذلك فانه لايمكن ان نجعل الconstructor دالة virtual function .

اي ان الكود التالي خاطئ خاطئ

وايضا اضافة الاخوة (رومانسي وخالد والدكتورة ساره ) جزاهم الله كل خير قد اكملت الموضوع ولو جزئيا واجابوا عن السؤال ..

لان اجابتي على سؤالك لا تتجاوز اكثر مما قالوه ..

اقتباس
داله البناء يجب أن لا تستدعي أيه داله virtual ...

صحيح.. وايضا عندما ينشأ الباني او (constructor ) .. فان Virtual Tabel يصبح غير مسموح به لان العملية هنا هي انشاء كائن .. ولكن عندما ننتهي من الكائن نهائيا .. نمسحه من البرنامج اومن الذاكرة او من كل مكان نهائيا .. فنستخدم virtual . .

رومانسي .. كلامك صحيح 100% ..

اقتباس
والقاعده دائما :

اذا كانت هناك أي داله virtual في الكلاس ، اذا داله الهدم سوف تكون أيضا virtual .

أيضا قاعده أخرى ، لكن قلما من كان بحاجه اليها ، ويكمن أن نتناقش حولها :

داله البناء يجب أن لا تستدعي أيه داله virtual ...

من يثبت هذه القاعده

فعندما نستخدم الكود التالي .. وهو اخر كود في الموضوع ..

#include <iostream.h>
class base
{
public:
virtual ~base()
{

}
};

class derived : public base
{
   public:
   ~derived()
   {

   }

};

void main()
{

   base *ptr = new derived();
   // some code
   delete ptr;
}

هنا النوع العام للمؤشر هو من الصنف الاب .. اما البيانات التي يشير اليها فهي من الصنف الابن ..

وعند استخدام ..

delete ptr;

اول ما يتوجه المترجم الى الصنف الابن لاحتوائه على دالة virtual وبعدها يحذف البيانات التي يشير اليها والتي هي من نوع الصنف الابن .

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

يَارَبُ إِن ضَاقَت قُلُوُب الْنَّاسٍ عَنْ مّافِي .. مِنْ خَيْرٍٍ فَعَفْوكَ لَا يَضِيْقْ ..

#11

أجابات صحيحه أخوان .. لكني سأقوم بتوضيحها مره أخرى ...

اقتباس
عملية الحصول على مؤشر الـ VTable المطلوب تتم خلال إنشاء الكائن. أليس كذلك

نعم ، وكما سنرى في المثال التالي أن الكائن الإبن لن يتم انشائه حقيقه الأ عند تنفيذ داله البناء الخاصه به .

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

ليكن لدينا كلاس يمثل عمليه Transaction ، ومن هذا الكلاس سوف ننشئ كلاسات أخرى أحدها تمثل عمليه البيع والأخرى الشراء . المهم عند حصول أي Transaction نريد أن نقوم هذه العمليه في ملف log .

يمكن حينها أن يكون التصميم بهذا الشكل :

class Transaction {							   // base class for all

public:										   // transactions

  Transaction();



  virtual void logTransaction() const = 0;	   // make type-dependent

												 // log entry

  ...

};

Transaction::Transaction()						// implementation of

{												 // base class ctor

  ...

  logTransaction();							   // as final action, log this

}												 // transaction

class BuyTransaction: public Transaction {		// derived class

public:

  virtual void logTransaction() const;		  // how to log transactions of this type



  ...

};

class SellTransaction: public Transaction {	  // derived class

public:

 virtual void logTransaction() const;		   // how to log trans-

												// actions of this type

  ...

};

الان في البرنامج الرئيسي وعند عمل كائن من أحد الكلاسات المشتقه ، مثلا :

BuyTransaction b;

الان هنا ، يقوم باستدعاء داله البناء في Transaction وسوف يصل الى الداله logTransaction وهنا تكون المفاجئه حيث لن يقوم باستدعاء داله logTransaction الموجوده في BuyTransaction ، ولكنه يقوم باستدعاء logTransaction الموجوده في Transaction !

لماذا ؟

لأن هذه طريقه سي++ في التعامل مع الكائنات عند استدعاء داله البناء الخاصه بالـ BaseClass ، حيث عند تنفيذ هذا الجزء تعتبر الكائن السابق b (والذي هو من نوع BuyTransaction ) نوعه Transaction، ولا يتعامل مع الكائن b على أساس انه مشتق Derived Class Object الا عندما يتم استدعاء داله البناء الخاصه به ..

اقتباس
During base class construction of a derived class object, the type of the object is that of the base class

An object doesn't become a derived class object until execution of a derived class constructor begins.

نرجع للكود السابق ، فهو لن يعمل في أغلب المترجمات حيث سيصدر خطأ ، وهو ان logTransaction في الكلاس Transaction داله Pure ، ولا تحتوي على Implementation .

المثال التالي هو نفسه المثال في الأعلى ، ولكن يقوم بعمل Implementation للداله الـ Virtual .

#include <iostream>
using namespace std;

class ClassA
{
	public :
		ClassA ()
		{	cout << "Constructor : ClassA\n";
			print();
		}

		virtual void print () 
		{
			cout << "Im ClassA !\n";
		}
};

class ClassB : public ClassA
{
	public :
		ClassB()
		{	cout << "Constructor : ClassB\n";	}

		virtual void print() 
		{	cout << "Im ClassB !\n";	}
};

int main ()
{
	ClassB b;
	return 0; 
}

قم يتشغيل البرنامج والناتج هو :

Constructor : ClassA

Im ClassA !

Constructor : ClassB

يجب عليك عند تصميمك لأي كلاس ان لا تستدعي في داله البناء والهدم أي داله Virtual مهما كان الثمن !!

حسنا ، ما الحل في تلك الحاله في مثال الTransaction ؟

أنت تريد أن تكتب في ملف log نوع العمليه عند عمل Object من أي كلاس تابع ل Transaction . وقد عرفنا أن استدعاء داله ملف log في داله البناء أمر خاطئ تماما .

هنا أمامك العديد من الحلول ، وهنا تبرز براعه المبرمج أو بالأصح خبير OOP & Design Pattern وهو ما المفترض أن تكون لدى اي مبرمج سي++ ،

المهم الحل قد يكمن في جعل الداله logTransaction عاديه (ليست Virtual ) في transaction ، وبعدها نطلب معلومات ملف log من أي كلاس مشتق من خلال داله البناء الخاصه بالكلاس المشتق (نرسلها عند عمل الكائن )، ومن ثم نرسلها الى داله البناء في transaction .

وهاهي القاعده مره أخرى لتتذكرها :

اقتباس
Don't call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of the currently executing constructor or destructor

من كتاب : Effictive C++ .

http://informatic-ar.com منصة تعليمية عربية في علوم الحاسب والبرمجة

https://moalfat.com  للكتب الالكترونية والكورسات التعليمية

Everything we see now is just an engineering solution based on old science

#12

شكرا يا رومانسي على التوضيح والاضافه ..

فعلا اصبح الموضوع مفيد لكل من يحتاج فهم هذه الدوال ..

وها انا اؤكد مره اخرى جملة رومانسي وهي ..

اقتباس
يجب عليك عند تصميمك لأي كلاس ان لا تستدعي في داله البناء والهدم أي داله Virtual مهما كان الثمن !!

فهي فهي مهمه جدا .. لكي ياتي اليوم اللي تجعلون به دالة ال contructor دالة virtual ...

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

يَارَبُ إِن ضَاقَت قُلُوُب الْنَّاسٍ عَنْ مّافِي .. مِنْ خَيْرٍٍ فَعَفْوكَ لَا يَضِيْقْ ..

هذا الموضوع مغلق.

مواضيع مشابهة