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

تنفيد الخصائص في لغة سي++

بدأه محمد رضى في 18 يوليو 2009 · 3 رد · 2,072 مشاهدة · في المواضيع والدروس
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

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

هذه كلاس استعملها لتنفيد الخصائص في برامج سي++ كما هو الحال بالنسبة للغات الأخرى كلغات الدوت نت. الشيء المهم في هذه الكلاس هو امكانية كتابة كود يبدو و كأنه يتحكم في متغيرة, مثال :

Test t;
t.value = 5;
Test tt;
tt.value = t.value;

بدل الكود العادي لـ سي++

Test t;
t.SetValue(5);
Test tt;
tt.SetValue(t.GetValue());

هذه الكلاس تستطيع التعامل مع كلتا طريقتي الكتابة أعلاه لتمكن كل واحد من التعامل مع الكلاس المنفدة للخصائص بالطريقة التي يريد.

هذه الكلاس هي إلى حد ما نوع من أنواع smart pointers حيث الكلاس تسير متغيرة لا يمكن للعناصر الخارجية الوصول إليها بطريقة مباشرة. و من هنا يمكن لنا التحكم في شروط قبول و رفض تغيير محتوى الخاصية و حتى رفض أو قبول قراءة محتواها. و الأهم من هذا هو القيام بتنفيد مهام بمجرد تغير في محتوى خاصية.

لإعلان خاصية :

property<C, T> name; // حيث C هو نوع الكلاس التي تحتوي على الخاصية و T هو نوع الخاصية و name هو اسم العنصر الحاوي للخاصية

بعد إعلان الخاصية يتوجب علينا تهيئتها للإستعمال و ذلك بزيادة دالتين الأولى تتكلف بقراءة محتوى الخاصية و الثانية تتكلف بتغيير محتواها. محتوى الخاصية ماهو إلا متغيرة من نوع T تتم قراءتها و كتابتها عن طريق الدالتين السابقتين. مثال

class Test
{
private :
	// هذه هي المتغيرة التي بداخلها محتوى الخاصية
	int i;
public :
	Test(void)
	{
		// تهيئة الخاصية
		value.Init(this, &Test::GetValue, &Test::SetValue);
	}

	// هذه هي الدالة التي تقبل أو ترفض تغيير محتوى الخاصية
	int &SetValue(const int &n)
	{
		// العلم وقت تغير محتوى الخاصية
		cout << "Propertie value Setter called.." << endl;
		if((n > 0) && (n < 10)) return i = 0; // إمكانية التحكم في محتوى الخصائص
		return i = n;
	}

	// هذه هي الدالة التي تقبل أو ترفض إعطائنا محتوى الخاصية
	int &GetValue(void)
	{
		//العلم وقت قراءة محتوى الخاصية
		cout << "Propertie value Getter called.." << endl;
		return i;
	}
public :
	// كيفية إنشاءو إعلان الخاصية
	property<Test, int> value;
};

الدالة Init هي التي تهيء الخاصية لتكون مقروءة فقط, مكتوبة فقط أم مقروءة و مكتوبة.

لإنشاء خاصية مقروءة فقط :

value.Init(this, &Test::GetValue, NULL);

لإنشاء خاصية مكتوبة فقط :

value.Init(this, NULL, &Test::SetValue);

لإنشاء خاصية مكتوبة و مقروءة في نفس الوقت :

value.Init(this, &Test::GetValue, &Test::SetValue);

بالطبع لا يمكن إنشاء خاصية لا مقروءة و لا مكتوبة.

للوصول إلى محتويات عنصر ممثل بخاصية نستعمل المعامل <- خصوصا إذا كان العنصر يمثل كلاس نريد الوصول إلى أحد العناصر المشتركة فيها.

للوصول مباشرة إلى محتوى الخاصية نستعمل المعامل *

هذا مثال مبسط لطريقة استعمال الخصائص يعتمد على الكلاس Test التي أنشأناها في المثال أعلاه :

void main(void)
{
	Test t, tt;

	t.value = 5;
	// إحالة محتوى خاصية لخاصية أخرى
	int i = tt.value = t.value;
	i = t.value;

	// مقارنة خاصيتين
	bool b = t.value == tt.value;
	b = t.value != tt.value;

	t.value = tt.value * 2;

	// مقارنة خاصية بمحتوى خاصية أخرى
	b = t.value == *tt.value;
	b = t.value != *tt.value;

	// طريقة التعامل العادية لـ سي++
	i = t.GetValue(); // تعادل i = t.value;
	tt.SetValue(i); // تعادل tt.value = i;
	t.SetValue(tt.GetValue()); // تعادل t.value = tt.value;
}

وفي الأخير, هذا الكود للكلاس التي تمكننا من تنفيد خصائص مشابهة للغات من نوع RAD ك لغات دوت نت و غيرها...

//كلاس الحاوية للخاصية
// C : نوع الكلاس الحاوية لـ property.
// T : نوع محتوى الخاصية
template <class C, typename T>
class property
{
public :
	// نوع المؤشر الذي ينادي على الدالة التي ترجع محتوى الخاصية
	typedef T &(C::*PGETTER)(void);
	// نوع المؤشر الذي ينادي على الدالة التي تغير محتوى الخاصية
	typedef T &(C::*PSETTER)(const T &value);

private :
	// مؤشر العنصر الحاوي للخاصية
	C *m_pObject;
	// مؤشر الدالة التي ترجع محتوى الخاصية
	PSETTER pSetter;
	// مؤشر الدالة التي تغير محتوى الخاصية
	PGETTER pGetter;
	// لضبط أن الخاصية لن تهيئ إلا مرة واحدة
	bool canInit;
public:
	// المنشئ الإفتراضي
	property(void)
	{
		m_pObject = NULL;
		pSetter = NULL;
		pGetter = NULL;
		canInit = true;
	}

	// تهيئة الخاصية
	// pObject   : العنصر الحاوي للخاصية
	// pGetter   : مؤشر الدالة التي ترجع محتوى الخاصية
	// pSetter   : مؤشر الدالة التي تغير محتوى الخاصية
	void Init(C *pObject, PGETTER pGetFunction, PSETTER pSetFunction)
	{
		if(!canInit) throw "Second initialisation exception!..";
		if(pObject == NULL) throw "Argument NULL : pObject";
		if(!pGetFunction && !pSetFunction) throw "Can't create non read/write property!..";
		m_pObject = pObject;
		this->pSetter = pSetFunction;
		this->pGetter = pGetFunction;
		canInit = false;
	}


	// هل يمكننا قراءة الخاصية
	bool CanRead(void)
	{
		return (this->pGetter != NULL);
	}

	// هل يمكننا كتابة الخاصية
	bool CanWrite(void)
	{
		return (this->pSetter != NULL);
	}

	// إرجاع محتوى الخاصية
	operator T()
	{
		// TODO : Assertion de m_pObject != NULL
		if(this->pGetter == NULL) throw "Read only propety!..";
		return (m_pObject->*pGetter)();
	}

	// إحالة معلومة إلى الخاصية
	T &operator =(const T& value)
	{
		// TODO : Assertion de m_pObject != NULL
		if(this->pSetter == NULL) throw "Read only Property!";
		return (m_pObject->*pSetter)(value);
	}

	// إحالة محتوى خاصية إلى خاصية أخرى
	T &operator =(const property<C, T> &prop)
	{
		if(this->pSetter == NULL) throw "Read only Property!";
		return (m_pObject->*pSetter)((prop.m_pObject->*pGetter)());
	}

	// مقارنة مساواة محتوى الخاصية بمتغير آخر
	bool operator==(const T& value)
	{
		// TODO : Assertion de m_pObject != NULL
		if(this->pGetter == NULL) throw "Write only property!..";
		return value == (m_pObject->*pGetter)();
	}

	// مقارنة مخالفة محتوى الخاصية بمتغير آخر
	bool operator!=(const T& value)
	{
		if(this->pGetter == NULL) throw "Write only property!..";
		return value != (m_pObject->*pGetter)();
	}

	// الوصول إلى محتوى الخاصية خصوصا إذا كانت حاوية لعنصر لكلاس
	T *operator->()
	{
		// TODO : Assertion de m_pObject != NULL
		if(this->pGetter == NULL) throw "Propriété en écriture seule!..";
		return (m_pObject->*pGetter)();
	}

	T &operator *(void)
	{
		if(this->pGetter == NULL) throw "Write only property!..";
		return (m_pObject->*pGetter)();
	}
};

لمعرفة إذا كانت الخاصية تقبل القراءة ننادي على الدالة CanRead()

لمعرفة إذا كانت الخاصية تقبل الكتابة أي تغيير المحتوى ننادي على الدالة CanWrite()

أرجو أن أكون قد تمكنت من شرح كيفية استعمال الكلاس property لتنفيد الخصائص في لغة سي++. و أرجو منكم الإبلاغ عن أي خطأ أو ثغرة كنت فد نسيت تصحيحها.

تحياتي...

تم تعديل هذه المشاركة بواسطة محمد رضى في 18 يوليو 2009 في 04:52

#2

بصراحة أخوي محمد

فكرة عبقرية , هذه مميزات لغة ال++C ... تستطيع عمل بها ما تريد ... خصوصاً الoperator overloading ...

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

	// كيفية إنشاءو إعلان الخاصية
	property<Test, int> value;

وهذا السطر فقط ...

		// تهيئة الخاصية
		value.Init(this, &Test::GetValue, &Test::SetValue);

وأنا أعتبرها فقط قليلة ... وبسيطة ...

على العموم , توجد مشكلة , وهي أنه كل عنصر يتكون من عدة مؤشرات ... لكن هذا لا يغير من أنها فكرة رائعة ...

شكراً

تم تعديل هذه المشاركة بواسطة b.m.s في 18 يوليو 2009 في 15:54

#3

شكرا أخي...

المشكل الذي أبحث له عن حل هو طريقة إضهار محتوى الخاصية وقت التشغيل من طرف VC Debugger. فأنا أحاول جاهدا "التفاوض معه" لإضهار محتوى الخاصية و ليس المؤشرات. فهذا يصعب إلى حد ما البحث عن الأخطاء...

كبداية, أعرف أن عناصر STL تعرف كيف؟

#4

الآن يمكننا قراءة محتوى الخاصية بكل سهولة وقت التشغيل من على نوافد Debugger فقبلا كان محتوى الخاصية يضهر بشكل

{m_pObject=0x879878 pGetter=0x565765 pSetter=0x696786}

أما الآن فالأمر سهل القراءة

{_value=123 __data{...}}.

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

//كلاس الحاوية للخاصية
// C : نوع الكلاس الحاوية لـ property.
// T : نوع محتوى الخاصية
template <class C, typename T>
class property
{
public :
	// نوع المؤشر الذي ينادي على الدالة التي ترجع محتوى الخاصية
	typedef T &(C::*PGETTER)(void);
	// نوع المؤشر الذي ينادي على الدالة التي تغير محتوى الخاصية
	typedef T &(C::*PSETTER)(const T &value);

private :
#ifdef _DEBUG
	// هذه المتغيرة التي تساعد على قراءة محتوى الخاصية وقت التشغيل
	// لن تكون موجودة عند الترجمة RELEASE
	T _value;
#endif // _DEBUG
	struct {
		// مؤشر العنصر الحاوي للخاصية
		C *pObject;
		// مؤشر الدالة التي ترجع محتوى الخاصية
		PSETTER pSetter;
		// مؤشر الدالة التي تغير محتوى الخاصية
		PGETTER pGetter;
		// لضبط أن الخاصية لن تهيئ إلا مرة واحدة
		bool canInit;
	} __data;
public:
	// المنشئ الإفتراضي
	property(void)
	{
		__data.pObject = NULL;
		__data.pSetter = NULL;
		__data.pGetter = NULL;
		__data.canInit = true;
	}

	// تهيئة الخاصية
	// pObject   : العنصر الحاوي للخاصية
	// pGetFunc   : مؤشر الدالة التي ترجع محتوى الخاصية
	// pSetFunc   : مؤشر الدالة التي تغير محتوى الخاصية
	void Init(C *pObject, PGETTER pGetFunc, PSETTER pSetFunc)
	{
		if(!__data.canInit) throw "Second initialisation exception!..";
		if(pObject == NULL) throw "Argument NULL : pObject";
		if(!pGetFunc && !pSetFunc) throw "Can't create non read/write property!..";
		__data.pObject = pObject;
		this->__data.pSetter = pSetFunc;
		this->__data.pGetter = pGetFunc;
		__data.canInit = false;
	}


	// هل يمكننا قراءة الخاصية
	bool CanRead(void)
	{
		return (this->__data.pGetter != NULL);
	}

	// هل يمكننا كتابة الخاصية
	bool CanWrite(void)
	{
		return (this->__data.pSetter != NULL);
	}

	// إرجاع محتوى الخاصية
	operator T()
	{
		// TODO : Assertion de __data.pObject != NULL
		if(this->__data.pGetter == NULL) throw "Read only propety!..";
		return (__data.pObject->*__data.pGetter)();
	}

	// إحالة معلومة إلى الخاصية
	T &operator =(const T& value)
	{
		// TODO : Assertion de __data.pObject != NULL
		if(this->__data.pSetter == NULL) throw "Read only Property!";
#ifdef _DEBUG
		return _value = (__data.pObject->*__data.pSetter)(value);
#else
		return (__data.pObject->*__data.pSetter)(value);
#endif // _DEBUG
	}

	// إحالة محتوى خاصية إلى خاصية أخرى
	T &operator =(const property<C, T> &prop)
	{
		if(this->__data.pSetter == NULL) throw "Read only Property!";
#ifdef _DEBUG
		return _value = (__data.pObject->*__data.pSetter)((prop.__data.pObject->*__data.pGetter)());
#else
		return (__data.pObject->*__data.pSetter)((prop.__data.pObject->*__data.pGetter)());
#endif // _DEBUG
	}

	// مقارنة مساواة محتوى الخاصية بمتغير آخر
	bool operator==(const T& value)
	{
		// TODO : Assertion de __data.pObject != NULL
		if(this->__data.pGetter == NULL) throw "Write only property!..";
		return value == (__data.pObject->*__data.pGetter)();
	}

	// مقارنة مخالفة محتوى الخاصية بمتغير آخر
	bool operator!=(const T& value)
	{
		if(this->__data.pGetter == NULL) throw "Write only property!..";
		return value != (__data.pObject->*__data.pGetter)();
	}

	// الوصول إلى محتوى الخاصية خصوصا إذا كانت حاوية لعنصر لكلاس
	T *operator->()
	{
		// TODO : Assertion de __data.pObject != NULL
		if(this->__data.pGetter == NULL) throw "Write only property!..";
		return (__data.pObject->*__data.pGetter)();
	}

	// الوصول مباشرة إلى محتوى الخاصية
	T &operator *(void)
	{
		if(this->__data.pGetter == NULL) throw "Write only property!..";
		return (__data.pObject->*__data.pGetter)();
	}
};
2

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