C ++ کی حکمت عملی لکھنے سے پہلے کچھ بنیادی معلومات جاننے کی ضرورت ہے ، کنگ بیجنگ کی مہارت کی تلاش نہ کریں ، کم از کم ان قواعد کو جانیں۔ ذیل میں نقل کیا گیا ہے:
اگر کوئی شخص اپنے آپ کو پروگرامر کہتا ہے اور میموری کے بارے میں کچھ نہیں جانتا ہے تو میں آپ کو بتا سکتا ہوں کہ وہ ضرور بڑبڑ رہا ہے۔ C یا C ++ میں پروگرام لکھنے کے لئے ، میموری پر زیادہ توجہ دینے کی ضرورت ہے ، اور یہ نہ صرف اس وجہ سے کہ میموری کی مناسب تقسیم کا پروگرام کی کارکردگی اور کارکردگی پر براہ راست اثر پڑتا ہے ، بلکہ اس سے بھی زیادہ اہم بات یہ ہے کہ جب ہم میموری کو چلانے کے دوران غلطی سے پریشانی کا سامنا کرنا پڑتا ہے ، اور بہت سے معاملات میں ، ان مسائل کو آسانی سے پتہ نہیں چلتا ہے ، جیسے میموری کا رساو ، جیسے پھانسی کا اشارہ۔ مصنف آج یہاں ان مسائل سے بچنے کے بارے میں بات کرنے کے لئے نہیں ہے ، بلکہ C ++ میموری آبجیکٹ کو ایک اور نقطہ نظر سے جاننے کے لئے ہے۔
ہم جانتے ہیں کہ C ++ میموری کو تین منطقی علاقوں میں تقسیم کرتا ہے: اسٹیک ، اسٹاک ، اور جامد اسٹوریج۔ چونکہ یہ ہے ، لہذا میں ان کے درمیان موجود اشیاء کو اسٹیک آبجیکٹ ، سلنڈر آبجیکٹ ، اور جامد آبجیکٹ کہتا ہوں۔ تو پھر ان مختلف میموری آبجیکٹ میں کیا فرق ہے؟ اسٹیک آبجیکٹ اور سلنڈر آبجیکٹ میں کیا فوائد اور نقصانات ہیں؟ اسٹیک آبجیکٹ یا سلنڈر آبجیکٹ بنانے پر پابندی کیسے لگائی جائے؟ یہ آج کا موضوع ہے۔
1 بنیادی تصورات
آئیے پہلے دیکھیں
Type stack_object ;
اسٹیک_ آبجیکٹ ایک سٹیک آبجیکٹ ہے جس کی زندگی اس کی تعریف کے نقطہ سے شروع ہوتی ہے ، اور جب اس کا فنکشن واپس آتا ہے تو اس کی زندگی ختم ہوجاتی ہے۔
اس کے علاوہ، تقریبا تمام عارضی آبجیکٹ فکسڈ آبجیکٹ ہیں۔ مثال کے طور پر، مندرجہ ذیل فنکشن کی تعریف:
Type fun(Type object);
یہ فنکشن کم از کم دو عارضی اشیاء پیدا کرتا ہے ، سب سے پہلے ، اس کی دلیل قدر کے طور پر دی جاتی ہے ، لہذا ایک عارضی آبجیکٹ object_copy1 پیدا کرنے کے لئے کاپی کنسٹرکشن فنکشن کو بلایا جاتا ہے۔ فنکشن کے اندر استعمال کیا جاتا ہے وہ آبجیکٹ نہیں ہے ، بلکہ آبجیکٹ_کوپی 1 ہے ، قدرتی طور پر ، آبجیکٹ_کوپی 1 ایک فکسڈ آبجیکٹ ہے ، جو جب فنکشن واپس آتا ہے تو اسے چھوڑ دیا جاتا ہے۔ اور یہ فنکشن ویلیو ریٹرننگ ہے ، جب فنکشن واپس آتا ہے ، اگر ہم واپسی ویلیو آپٹیمائزڈ ((NRV) کو نظرانداز کرتے ہیں تو ، پھر ایک عارضی آبجیکٹ object_copy2 بھی پیدا ہوتا ہے ، جو عارضی آبجیکٹ کو واپس کرتا ہے جو فنکشن کے بعد کچھ وقت کے لئے چھوڑ دیا جاتا ہے۔ مثال کے طور پر ، کسی فنکشن میں مندرجہ ذیل کوڈ ہوتا ہے:
Type tt ,result ; //生成两个栈对象
tt = fun(tt); //函数返回时,生成的是一个临时对象object_copy2
مندرجہ بالا دوسری عبارت کا اطلاق اس طرح ہوتا ہے کہ پہلے فنکشن کے واپسی پر ایک عارضی آبجیکٹ object_copy2 پیدا کیا جاتا ہے اور پھر اس کے بعد اسٹریٹج آپریٹر کو کال کیا جاتا ہے
tt = object_copy2 ; //调用赋值运算符
کیا آپ نے دیکھا؟ کمپائلر نے ہمارے لئے بہت سے عارضی آبجیکٹ بنائے ہیں ، ہمارے شعور کے بغیر ، اور ان عارضی آبجیکٹوں کو پیدا کرنے کے لئے وقت اور جگہ کی لاگت بہت زیادہ ہوسکتی ہے ، لہذا ، آپ کو شاید یہ سمجھ آ جائے گا کہ یہ کیوں بہتر ہے کہ کنسٹ حوالہ جات کو منتقل کرنے کے بجائے قدر کے لحاظ سے فنکشن پیرامیٹرز کو منتقل کریں۔
اس کے بعد ، ڈھیر کو دیکھیں۔ ڈھیر ، جسے فری اسٹوریج ایریا بھی کہا جاتا ہے ، پروگرام کے عمل کے دوران متحرک طور پر مختص کیا جاتا ہے ، لہذا اس کی سب سے بڑی خصوصیت متحرک ہے۔ C ++ میں ، تمام اسٹیک آبجیکٹ کی تخلیق اور تباہی کا ذمہ دار پروگرامر ہوتا ہے ، لہذا ، اگر اس کو خراب طریقے سے سنبھالا جائے تو ، میموری کی پریشانی پیدا ہوگی۔ اگر اسٹیک آبجیکٹ کو مختص کیا گیا ہے ، لیکن اسے آزاد کرنا بھول گیا ہے تو ، میموری لیک ہوجائے گا۔ اور اگر آبجیکٹ کو آزاد کردیا گیا ہے ، لیکن اس کے مطابق پوائنٹر کو NULL کے طور پر مقرر نہیں کیا گیا ہے ، تو یہ پوائنٹر نام نہاد پھانسی پوائنٹر ہے ، اور جب اس پوائنٹر کا دوبارہ استعمال کیا جاتا ہے تو ، غیر قانونی رسائی کا سامنا کرنا پڑتا ہے ، جس کی وجہ سے پروگرام گرنے کا سبب بنتا ہے۔
تو ، C ++ میں اسٹیک آبجیکٹ کو کس طرح تقسیم کیا جاتا ہے؟ اس کا واحد طریقہ یہ ہے کہ نیا () استعمال کیا جائے (یقینا ، C قسم کے اسٹیک میموری کے لئے malloc ہدایت بھی دستیاب ہے) ۔ جب نیا استعمال کیا جاتا ہے تو ، اسٹیک میں ایک میموری مختص کی جاتی ہے اور اس اسٹیک آبجیکٹ کی طرف اشارہ کرنے والے پوائنٹر کو واپس کیا جاتا ہے۔
ایک بار پھر ، جامد اسٹوریج زون پر نظر ڈالیں۔ تمام جامد اشیاء ، عالمی اشیاء کو جامد اسٹوریج زون میں مختص کیا گیا ہے۔ عالمی اشیاء کے بارے میں ، یہ اہم () فنکشن کے عمل سے پہلے ہی مختص کیا گیا ہے۔ دراصل ، اہم () فنکشن میں ڈسپلے کوڈ کے عمل سے پہلے ، کمپریسر کے ذریعہ تیار کردہ _ مین () فنکشن کو بلایا جاتا ہے ، جبکہ _ مین () فنکشن تمام عالمی اشیاء کی تشکیل اور ابتداء کا کام کرتا ہے۔ اہم () فنکشن کے اختتام سے پہلے ، تمام عالمی اشیاء کو آزاد کرنے کے لئے کمپریسر کے ذریعہ تیار کردہ ایکسٹ فنکشن کو بلایا جاتا ہے۔
void main(void)
{
... // 显式代码
}
// 实际上转化为这样:
void main(void)
{
_main(); //隐式代码,由编译器产生,用以构造所有全局对象
... // 显式代码
...
exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象
}
تو ، یہ جان کر ، اس سے کچھ تدبیریں نکالی جاسکتی ہیں ، جیسے ، فرض کریں کہ ہم main () فنکشن کے نفاذ سے پہلے کچھ تیاری کرنا چاہتے ہیں ، پھر ہم ان تیاریوں کو ایک اپنی مرضی کے مطابق گلوبل آبجیکٹ کے تعمیراتی فنکشن میں لکھ سکتے ہیں ، تاکہ ، main () فنکشن کے ظاہری کوڈ کے نفاذ سے پہلے ، اس گلوبل آبجیکٹ کے تعمیراتی فنکشن کو بلایا جائے ، اور اس طرح ہمارے مقصد کو پورا کیا جائے۔ ہم نے ابھی جامد اسٹوریج کے علاقے میں گلوبل آبجیکٹ کی بات کی ہے ، تو ، کیا مقامی جامد آبجیکٹ؟ مقامی جامد آبجیکٹ بھی عام طور پر افعال میں بیان کیا جاتا ہے ، جیسے کہ آبجیکٹ خرگوش ، صرف اس کے سامنے ایک سے زیادہ جامد کلیدی الفاظ ہیں۔ مقامی جامد آبجیکٹ کی زندگی اس وقت سے ہوتی ہے جب اس کا فنکشن پہلی بار بلایا جاتا ہے ، یا اس سے بھی زیادہ ، جب اس جامد آبجیکٹ کو پہلی بار اسٹیٹ کوڈ کا اعلان کیا جاتا ہے ، اور اس وقت تک جب تک کہ اس کا پورا پروگرام ختم نہیں ہوتا ہے ، تو ،
ایک اور قسم کا جامد آبجیکٹ ہے جو کلاس کا جامد رکن ہے۔ اس صورت حال کو مدنظر رکھتے ہوئے ، کچھ زیادہ پیچیدہ مسائل شامل ہیں۔
پہلا مسئلہ کلاس کے جامد رکن آبجیکٹ کی عمر ہے، کلاس کا جامد رکن آبجیکٹ پہلی کلاس آبجیکٹ کے پیدا ہونے کے ساتھ پیدا ہوتا ہے، اور پورے عمل کے اختتام پر غائب ہوجاتا ہے۔ یعنی ایسی صورت حال موجود ہے، کہ پروگرام میں ہم نے ایک کلاس کی تعریف کی، جس میں اس کلاس میں ایک جامد آبجیکٹ بطور رکن ہے، لیکن عمل کے دوران، اگر ہم نے اس کلاس کا کوئی بھی آبجیکٹ نہیں بنایا، تو پھر اس کلاس میں شامل وہ جامد آبجیکٹ بھی پیدا نہیں ہوگا۔ اور، اگر کثیر کلاس آبجیکٹ بنائے گئے ہیں، تو پھر ان سبھی آبجیکٹوں نے اس جامد آبجیکٹ کے رکن کو شیئر کیا۔
دوسرا مسئلہ یہ ہے کہ جب:
class Base
{
public:
static Type s_object ;
}
class Derived1 : public Base / / 公共继承
{
... // other data
}
class Derived2 : public Base / / 公共继承
{
... // other data
}
Base example ;
Derivde1 example1 ;
Derivde2 example2 ;
example.s_object = …… ;
example1.s_object = …… ;
example2.s_object = …… ;
براہ کرم نوٹ کریں کہ اوپر بلیک بائٹ کے طور پر نشان زد تینوں بیانات میں ، کیا s_object ایک ہی آبجیکٹ کا دورہ کر رہا ہے؟ جواب ہاں میں ہے ، اور وہ واقعی ایک ہی آبجیکٹ کی طرف اشارہ کرتے ہیں ، یہ سچ نہیں لگتا ہے ، ہے نا؟ لیکن یہ سچ ہے ، آپ اپنے آپ کو ایک سادہ سیکشن لکھ کر اس کی تصدیق کرسکتے ہیں۔ میں اس کی وضاحت کرنے کی کوشش کر رہا ہوں کہ ایسا کیوں ہوتا ہے۔ ہم جانتے ہیں کہ جب ایک کلاس جیسے Derived1 دوسری کلاس جیسے بیس سے وراثت کرتی ہے تو ، ہم دیکھ سکتے ہیں کہ ایک Derived1 آبجیکٹ میں ایک بیس قسم کا آبجیکٹ ہوتا ہے ، جو ایک subobject ہے۔
آئیے سوچتے ہیں کہ جب ہم Derived1 قسم کا ایک آبجیکٹ کسی ایسے فنکشن کو دیتے ہیں جو غیر حوالہ بیس قسم کے پیرامیٹرز کو قبول کرتا ہے تو اس میں کٹائی ہوتی ہے، تو پھر کٹائی کیسے ہوتی ہے؟ یقین ہے کہ اب آپ جانتے ہیں، یہ صرف Derived1 قسم کے آبجیکٹ میں سے ذیلی آبجیکٹ کو نکالتا ہے، اور تمام دیگر اعداد و شمار کے ارکان کو نظر انداز کرتا ہے جو Derived1 اپنی مرضی کے مطابق ہے، اور پھر اس ذیلی آبجیکٹ کو فنکشن میں منتقل کرتا ہے (واقعی، فنکشن اس ذیلی آبجیکٹ کی کاپی استعمال کرتا ہے)
تمام وراثت بیس قسم کے مشتق طبقے کے آبجیکٹ میں ایک بیس قسم کا ذیلی آبجیکٹ ہوتا ہے (یہ وہ جگہ ہے جہاں بیس قسم کے اشارے سے ڈیریویڈ 1 آبجیکٹ کی کلید کی طرف اشارہ کیا جاسکتا ہے ، جو قدرتی طور پر ایک کثیرالاضلاع کی کلید ہے) ، اور تمام ذیلی آبجیکٹ اور تمام بیس قسم کے آبجیکٹ ایک ہی s_object آبجیکٹ کا اشتراک کرتے ہیں۔ قدرتی طور پر ، بیس قسم سے ماخوذ پورے وراثت کے نظام میں کلاسوں کے تمام واقعات ایک ہی s_object آبجیکٹ کا اشتراک کرتے ہیں۔ مندرجہ بالا مثال ، مثال 1 ، مثال 2 میں آبجیکٹ کی ترتیب مندرجہ ذیل ہے:
2 تین میموری اشیاء کا موازنہ
اس کے علاوہ ، اس کی تخلیق کی رفتار عام طور پر اسٹیک آبجیکٹ سے زیادہ تیز ہوتی ہے ، کیونکہ اسٹیک آبجیکٹ کو تقسیم کرتے وقت ، آپریٹر نیو آپریشن کو کال کیا جاتا ہے۔ آپریٹر نیو میموری اسپیس سرچنگ الگورتھم کا استعمال کرتا ہے ، اور یہ تلاش کا عمل بہت وقت طلب ہوسکتا ہے۔ اس کے نتیجے میں ، اسٹیک آبجیکٹ پیدا کرنے میں اتنی پریشانی نہیں ہوتی ہے ، اس کے لئے صرف اسٹیک کاؤنٹر کو منتقل کرنے کی ضرورت ہوتی ہے۔
اسٹیک آبجیکٹ ، اس کی تخلیق اور اس کی تباہی کے لمحات کو پروگرامر کی طرف سے واضح طور پر بیان کیا جانا چاہئے ، یعنی اسٹیک آبجیکٹ کی زندگی پر پروگرامر کا مکمل کنٹرول ہے۔ ہمیں اکثر ایسی اشیاء کی ضرورت ہوتی ہے ، مثال کے طور پر ، ہمیں ایک ایسا آبجیکٹ بنانے کی ضرورت ہوتی ہے جو متعدد افعال کے ذریعہ قابل رسائی ہو ، لیکن ہم اسے عالمی نہیں بنانا چاہتے ہیں۔ اس وقت اسٹیک آبجیکٹ بنانا بلا شبہ ایک اچھا انتخاب ہے ، اور پھر اس اسٹیک آبجیکٹ کا اشارہ ہر فنکشن کے مابین منتقل کرنا ، جس سے اس آبجیکٹ کا اشتراک کیا جاسکتا ہے۔ اس کے علاوہ ، اسٹیک کی جگہ کے مقابلے میں اسٹیک کی گنجائش بہت زیادہ ہے۔ در حقیقت ، جب جسمانی میموری کافی نہیں ہوتی ہے ، اگر اس وقت بھی نئے اسٹیک آبجیکٹ کی تخلیق کی ضرورت ہوتی ہے تو ، عام طور پر اس وقت غلطی پیدا نہیں ہوتی ہے جب یہ چلتا ہے ، بلکہ ورچوئل میموری کو اصل جسمانی میموری کو بڑھانے کے لئے استعمال کیا جاتا ہے۔
اس کے بعد ہم دیکھیں گے کہ اسٹیٹک آبجیکٹ کیا ہے؟
سب سے پہلے ، عالمی اشیاء۔ عالمی اشیاء کلاسوں اور افعال کے مابین مواصلات کے لئے ایک آسان ترین طریقہ فراہم کرتی ہیں ، حالانکہ یہ خوبصورت نہیں ہے۔ عام طور پر ، مکمل طور پر آبجیکٹ پر مبنی زبانوں میں ، عالمی اشیاء موجود نہیں ہیں ، جیسے C # ، کیونکہ عالمی اشیاء کا مطلب غیر محفوظ اور اعلی انضمام ہے ، اور پروگرام میں عالمی اشیاء کا زیادہ استعمال پروگرام کی طاقت ، استحکام ، بحالی اور استحکام کو بہت کم کرتا ہے۔ C ++ بھی عالمی اشیاء کو مکمل طور پر ختم کرسکتا ہے ، لیکن آخر کار ایسا نہیں ، میرے خیال میں ایک وجہ یہ ہے کہ C کے ساتھ مطابقت پذیر ہے۔
پھر کلاس کا جامد ممبر۔ جیسا کہ اوپر بتایا گیا ہے کہ تمام آبجیکٹ اس جامد ممبر کو شیئر کرتے ہیں۔ لہذا جب ان کلاسوں کے درمیان یا ان کلاس آبجیکٹ کے درمیان ڈیٹا شیئرنگ یا مواصلات کی ضرورت ہو تو اس طرح کے جامد ممبر بلا شبہ ایک اچھا انتخاب ہیں۔
اس کے بعد جامد مقامی آبجیکٹ ہیں ، جو بنیادی طور پر اس چیز کو برقرار رکھنے کے لئے استعمال کیا جاتا ہے جس کی حیثیت اس وقت ہوتی ہے جب فنکشن کو بار بار بلایا جاتا ہے۔ اس کی سب سے نمایاں مثال ریکرنسی فنکشن ہے۔ ہم سب جانتے ہیں کہ ریکرنسی فنکشن خود اپنے آپ کو بلاتا ہے۔ اگر ریکرنسی فنکشن میں ایک نان اسٹیٹک مقامی آبجیکٹ کی وضاحت کی جاتی ہے تو ، جب ریکرنسی کی تعداد کافی زیادہ ہوتی ہے تو ، اس کی قیمت بھی بہت زیادہ ہوتی ہے۔ اس کی وجہ یہ ہے کہ نان اسٹیٹک مقامی آبجیکٹ خرگوش آبجیکٹ ہے ، ہر بار بار بار کال کرنے پر ، اس طرح کا ایک آبجیکٹ پیدا ہوتا ہے ، اور ہر بار واپس آنے پر ، اس آبجیکٹ کو آزاد کردیا جاتا ہے ، اور ، اس طرح کے آبجیکٹ صرف موجودہ کال کی پرت تک ہی محدود ہیں ، جو گہری سرایت پرت اور زیادہ بے نقاب پرتوں کے لئے پوشیدہ ہیں۔ ہر مقامی آبجیکٹ کا اپنا عنصر اور پیرامیٹر ہوتا ہے۔
ریورس فنکشن ڈیزائن میں ، جامد اشیاء کو غیر جامد مقامی اشیاء کے بجائے استعمال کیا جاسکتا ہے (یعنی فکسڈ آبجیکٹ) ، جس سے نہ صرف ہر بار ریورس کال اور واپسی پر غیر جامد اشیاء کی تخلیق اور رہائی کی لاگت کو کم کیا جاسکتا ہے ، بلکہ جامد اشیاء بھی ریورس کال کی درمیانی حالت کو محفوظ کرسکتے ہیں ، اور ہر کال کی پرت کے لئے قابل رسائی ہیں۔
3 غیر متوقع طور پر استعمال شدہ کھیتی
جیسا کہ پہلے بتایا گیا ہے کہ جب مناسب وقت آتا ہے تو اس کے بعد خود کار طریقے سے جاری کیا جاتا ہے ، یعنی جب اس کی خود کار طریقے سے انتظامیہ ہوتی ہے۔ تو ، جب اس کی زندگی ختم ہوجاتی ہے تو اس کی خود کار طریقے سے رہائی ہوتی ہے۔ پہلے ، اس کی زندگی کے اختتام پر۔ دوسرا ، جب اس میں موجود فنکشن میں کوئی غیر معمولی واقع ہوتا ہے۔ آپ شاید کہیں گے کہ یہ سب کچھ معمول کی بات ہے۔ ہاں ، اس میں کوئی بڑی بات نہیں ہے۔ لیکن جب تک ہم تھوڑا سا گہرائی میں جائیں گے ، شاید کچھ غیر متوقع کٹائی ہو۔
جب کوئی شیلف آبجیکٹ خود بخود جاری ہوتا ہے تو ، اس کا اپنا تجزیہ فنکشن بلایا جاتا ہے۔ اگر ہم کسی شیلف آبجیکٹ میں وسائل کو بند کردیں ، اور اس کے تجزیہ فنکشن میں وسائل کو آزاد کرنے کے لئے کارروائی کریں ، تو اس سے وسائل کے رسک ہونے کا امکان بہت کم ہوجاتا ہے ، کیونکہ جب کوئی شیلف آبجیکٹ خود بخود وسائل کو آزاد کرسکتا ہے ، یہاں تک کہ اگر اس میں کوئی غیر معمولی کام ہوتا ہے۔ اصل عمل یہ ہے کہ جب کوئی فنکشن غیر معمولی طور پر خارج ہوجاتا ہے تو ، اس کو اسٹیک_ونڈ کہتے ہیں (اسٹاک_ونڈ) ، یعنی اسٹیک کھل جاتا ہے۔ چونکہ شیلف آبجیکٹ ، قدرتی شیلف میں موجود ہے ، اسٹیک کی واپسی کے عمل میں شیلف آبجیکٹ کا تجزیہ فنکشن انجام دیا جاتا ہے ، اس طرح اس کے انعقاد شدہ وسائل کو آزاد کردیا جاتا ہے۔ جب تک کہ اسٹیک فنکشن کے نفاذ کے دوران اس غیر معمولی کو دوبارہ نہیں چھوڑا جاتا ہے ، لیکن اس کا امکان بہت کم ہے ، لہذا اس طرح کے وسائل کو بند کرنے کے لئے شیلف کو
4 اسٹیک آبجیکٹ بنانے پر پابندی
جیسا کہ اوپر بیان کیا گیا ہے ، اگر آپ نے کسی قسم کے اسٹیک آبجیکٹ کو پیدا کرنے سے منع کرنے کا فیصلہ کیا ہے تو ، آپ خود ہی ایک ریسورس پیکجنگ کلاس تشکیل دے سکتے ہیں جو صرف اسٹیک میں پیدا ہوسکتی ہے ، اور اس طرح غیر معمولی حالات میں خود بخود اسٹیک کو آزاد کیا جاسکتا ہے۔
تو اسٹیک آبجیکٹ بنانے سے کیسے روکا جائے؟ ہم جانتے ہیں کہ اسٹیک آبجیکٹ بنانے کا واحد طریقہ new آپریشن کا استعمال کرنا ہے، اگر ہم new استعمال کرنے سے منع کریں تو کیا یہ کام نہیں کرے گا۔ مزید آگے، new آپریشن کے عمل کے وقت آپریٹر new کو بلایا جائے گا، اور آپریٹر new کو دوبارہ لوڈ کیا جا سکتا ہے۔ طریقہ یہ ہے کہ new آپریٹر کو پرائیویٹ بنایا جائے، تاکہ ہم آہنگی کے لئے، آپریٹر delete کو بھی دوبارہ لوڈ کیا جا سکے۔ اب، آپ شاید یہ سوال پوچھ رہے ہیں کہ کیا آپ کوک آبجیکٹ بنانے کے لیے new کو بلانے کی ضرورت نہیں ہے؟ جی ہاں، ضرورت نہیں، کیونکہ آپ کوک آبجیکٹ بنانے کے لیے میموری کو سرچ کرنے کی ضرورت نہیں ہوتی، بلکہ براہ راست اسٹیک کا پوائنٹر ایڈجسٹ کرکے آبجیکٹ کو دبائیں، اور new آپریٹر کا بنیادی کام مناسب اسٹیک میموری کو سرچ کرنا ہے، اور اسٹیک آبجیکٹ کے لیے جگہ مختص کرنا ہے، جس کا ذکر اوپر کیا گیا ہے۔
#include <stdlib.h> //需要用到C式内存分配函数
class Resource ; //代表需要被封装的资源类
class NoHashObject
{
private:
Resource* ptr ;//指向被封装的资源
... ... //其它数据成员
void* operator new(size_t size) //非严格实现,仅作示意之用
{
return malloc(size) ;
}
void operator delete(void* pp) //非严格实现,仅作示意之用
{
free(pp) ;
}
public:
NoHashObject()
{
//此处可以获得需要封装的资源,并让ptr指针指向该资源
ptr = new Resource() ;
}
~NoHashObject()
{
delete ptr ; //释放封装的资源
}
};
NoHashObject اب ایک ایسی کلاس ہے جس میں اسٹیک آبجیکٹ پر پابندی عائد ہے ، اگر آپ مندرجہ ذیل کوڈ لکھتے ہیں:
NoHashObject* fp = new NoHashObject() ؛ // مرتب کرنے میں خرابی!
delete fp ;
مندرجہ بالا کوڈ میں مرتب وقت کی غلطیاں پیدا ہوں گی۔ ٹھیک ہے ، اب جب آپ جانتے ہیں کہ اسٹیک آبجیکٹ کو ممنوع قرار دینے والی کلاس کو کس طرح ڈیزائن کیا جائے ، تو آپ کو بھی میرے جیسے سوالات ہوسکتے ہیں ، کیا اس قسم کی اسٹیک آبجیکٹ کو اس صورت میں پیدا نہیں کیا جاسکتا ہے جب کلاس NoHashObject کی تعریف کو تبدیل نہیں کیا جاسکتا ہے؟ نہیں ، یا ایسا کرنے کا کوئی طریقہ ہے ، جسے میں نے چنگاری تشدد ہیک کرنے کا طریقہ کہا ہے۔ C ++ اتنا طاقتور ہے کہ آپ اس کے ساتھ جو کچھ بھی کرنا چاہتے ہو کر سکتے ہیں۔ یہاں بنیادی طور پر استعمال کی جانے والی تکنیک پوائنٹر قسم کی جبری تبدیلی ہے۔
void main(void)
{
char* temp = new char[sizeof(NoHashObject)] ;
//强制类型转换,现在ptr是一个指向NoHashObject对象的指针
NoHashObject* obj_ptr = (NoHashObject*)temp ;
temp = NULL ; //防止通过temp指针修改NoHashObject对象
//再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员
Resource* rp = (Resource*)obj_ptr ;
//初始化obj_ptr指向的NoHashObject对象的ptr成员
rp = new Resource() ;
//现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
... ...
delete rp ;//释放资源
temp = (char*)obj_ptr ;
obj_ptr = NULL ;//防止悬挂指针产生
delete [] temp ;//释放NoHashObject对象所占的堆空间。
}
مندرجہ بالا نفاذ مشکل ہے اور اس نفاذ کا طریقہ عملی طور پر استعمال نہیں کیا جاتا ہے ، لیکن میں نے اس کا راستہ لکھا ہے ، کیونکہ اس کو سمجھنا ہمارے لئے C ++ میموری آبجیکٹ کو سمجھنے کے لئے فائدہ مند ہے۔ مندرجہ بالا بہت سے مجبور قسم کے تبادلوں کے لئے ، اس کی بنیادی بات کیا ہے؟ ہم اس طرح سمجھ سکتے ہیں:
میموری میں موجود اعداد و شمار میں کوئی تبدیلی نہیں ہوتی ہے، اور اس کی قسم وہ عینک ہے جو ہم پہنے ہوئے ہیں۔ جب ہم ایک قسم کی عینک پہنے ہوئے ہوتے ہیں تو ہم میموری میں موجود اعداد و شمار کو اس کی قسم کے مطابق تشریح کرتے ہیں، اس طرح مختلف تشریحات سے مختلف معلومات حاصل ہوتی ہیں۔
جبری ٹائپ ٹرانسفارمیشن کا مطلب یہ ہے کہ آپ ایک اور عینک پہن کر پھر اسی میموری ڈیٹا کو دوبارہ دیکھیں۔
یہ بھی یاد رکھنا ضروری ہے کہ مختلف کمپائلرز آبجیکٹ کے ممبر ڈیٹا کی ترتیب کو مختلف طریقے سے ترتیب دے سکتے ہیں۔ مثال کے طور پر ، زیادہ تر کمپائلرز NoHashObject کے ptr پوائنٹر ممبروں کو آبجیکٹ کی جگہ کے پہلے 4 بائٹس میں ترتیب دیتے ہیں ، اس طرح صرف اس بات کی ضمانت دی جاتی ہے کہ اس بیان میں تبدیلی کا عمل اسی طرح انجام پائے گا جس کی ہم توقع کرتے ہیں:
Resource* rp = (Resource*)obj_ptr ;
تاہم، یہ ضروری نہیں ہے کہ تمام کمپائلرز ایسا ہی کریں۔
اگر ہم کسی خاص قسم کے اسٹیک آبجیکٹ کو پیدا کرنے سے منع کر سکتے ہیں تو کیا ہم ایسی کلاس بنا سکتے ہیں جس سے اسٹیک آبجیکٹ پیدا نہ ہو؟
5 کتے کی تخلیق پر پابندی
جیسا کہ پہلے ذکر کیا گیا ہے ، جب کسی کوپ آبجیکٹ کی تخلیق کی جاتی ہے تو ، کوپ اشارے کو کوپ سے مناسب سائز کی جگہ نکالنے کے لئے منتقل کیا جاتا ہے ، اور پھر اس جگہ پر براہ راست ایک کوپ آبجیکٹ بنانے کے لئے متعلقہ تعمیراتی فنکشن کو کال کیا جاتا ہے ، اور جب فنکشن واپس آتا ہے تو ، اس کے تجزیاتی فنکشن کو اس آبجیکٹ کو آزاد کرنے کے لئے بلایا جاتا ہے ، اور پھر کوپ اشارے کو کوپ کو ایڈجسٹ کیا جاتا ہے۔ اس عمل میں آپریٹر نیو / ڈیلیٹ کی ضرورت نہیں ہے ، لہذا آپریٹر نیو / ڈیلیٹ کو نجی کے طور پر سیٹ کرنا مقصد کو پورا نہیں کرسکتا ہے۔
یہ ممکن ہے ، اور میں اس طرح کا استعمال کرنے کا ارادہ رکھتا ہوں۔ لیکن اس سے پہلے ، ایک چیز پر غور کرنے کی ضرورت ہے ، یعنی ، اگر ہم تعمیراتی فنکشن کو نجی پر سیٹ کرتے ہیں تو ، ہم اس کے ساتھ براہ راست ڈھیر بنانے کے لئے بھی استعمال نہیں کرسکتے ہیں کیونکہ جب نئی اشیاء کو جگہ مختص کرنے کے بعد اس کی تعمیراتی فنکشن کو بھی بلایا جائے گا۔ لہذا ، میں صرف تجزیہ فنکشن کو نجی پر سیٹ کرنے کا ارادہ رکھتا ہوں۔ مزید تجزیہ ، کیا تعمیراتی فنکشن کو نجی کے طور پر سیٹ کرنے کے علاوہ کسی اور اثر کو بھی محدود کیا جائے گا؟ ہاں ، اس سے وراثت بھی محدود ہوجائے گی۔
اگر ایک کلاس بیس کلاس کا ارادہ نہیں رکھتی ہے تو ، عام طور پر اس کا تجزیہ فنکشن کو نجی قرار دینا ہوتا ہے۔
اس کے علاوہ ، اگر ہم کسی بھی قسم کے ورثے کو محدود نہیں کرتے ہیں تو ، ہم اس کو محفوظ قرار دے سکتے ہیں۔
class NoStackObject
{
protected:
~NoStackObject() { }
public:
void destroy()
{
delete this ;//调用保护析构函数
}
};
اس کے بعد ، آپ NoStackObject کلاس کو اس طرح استعمال کرسکتے ہیں:
NoStackObject* hash_ptr = new NoStackObject() ;
… … // hash_ptr اشارہ کردہ آبجیکٹ پر کارروائی کریں
hash_ptr->destroy() ; ارے ، کیا یہ تھوڑا سا عجیب لگتا ہے کہ ہم نے new کے ساتھ ایک آبجیکٹ بنایا ، لیکن اسے حذف کرنے کے لئے اسے حذف کرنے کے بجائے ، اسے تباہ کرنے کا طریقہ استعمال کیا۔ واضح طور پر ، صارفین اس طرح کے عجیب و غریب استعمال کے عادی نہیں ہیں۔ لہذا ، میں نے تعمیراتی فنکشن کو بھی نجی یا محفوظ کرنے کا فیصلہ کیا۔ یہ اس سوال کی طرف واپس آتا ہے جس سے ہم نے بچنے کی کوشش کی تھی ، یعنی new کا استعمال نہیں کیا ، تو پھر کسی آبجیکٹ کو کس طرح پیدا کیا جائے؟ ہم اس کو بالواسطہ طریقے سے کرسکتے ہیں ، یعنی اس کلاس کو ایک جامد ممبر فنکشن مہیا کریں جو خاص طور پر اس قسم کے ڈھیر کو پیدا کرنے کے لئے ہے۔
class NoStackObject
{
protected:
NoStackObject() { }
~NoStackObject() { }
public:
static NoStackObject* creatInstance()
{
return new NoStackObject() ;//调用保护的构造函数
}
void destroy()
{
delete this ;//调用保护的析构函数
}
};
اب آپ NoStackObject کلاس کو اس طرح استعمال کر سکتے ہیں:
NoStackObject* hash_ptr = NoStackObject::creatInstance() ;
… … // hash_ptr اشارہ کردہ آبجیکٹ پر کارروائی کریں
hash_ptr->destroy() ;
hash_ptr = NULL ؛ // پھانسی کے اشارے کے استعمال کو روکتا ہے
اب یہ بہتر محسوس ہوتا ہے کہ پیدا کرنے والے اور جاری کرنے والے کا عمل ایک جیسا ہے۔
بہت سے C یا C ++ پروگرامرز کو ردی کی ٹوکری کو ری سائیکل کرنے سے نفرت ہے ، یہ سوچتے ہوئے کہ ردی کی ٹوکری کو یقینی طور پر متحرک میموری کا انتظام کرنے سے کم موثر ہے ، اور اس کی بازیابی کے وقت یہ یقینی طور پر پروگرام کو روک دے گا ، اور اگر آپ میموری مینجمنٹ کو کنٹرول کرتے ہیں تو ، تقسیم اور رہائی کا وقت مستحکم ہے ، جس سے پروگرام کو روکنے کا سبب نہیں بنتا ہے۔ آخر میں ، بہت سے C / C ++ پروگرامرز اس بات پر یقین رکھتے ہیں کہ C / C ++ میں ردی کی ٹوکری کو ری سائیکل کرنے کا کوئی طریقہ کار نہیں ہے۔ یہ غلط خیالات ردی کی ٹوکری کو ری سائیکل کرنے کے الگورتھم کو نہ جاننے کی وجہ سے تیار کیے گئے ہیں۔
حقیقت میں ردی کی ٹوکری کی بازیابی کا طریقہ کار سست نہیں ہے ، بلکہ متحرک میموری کی تقسیم سے بھی زیادہ موثر ہے۔ چونکہ ہم صرف تقسیم نہیں کرسکتے ہیں ، لہذا میموری کی تقسیم کے وقت صرف ڈھیر سے نئی میموری حاصل کرنے کی ضرورت ہوتی ہے ، ڈھیر کے سرے کو منتقل کرنے کے لئے اشارے کافی ہیں۔ اور اس کے لئے جاری عمل کو نظرانداز کیا گیا ہے ، اور قدرتی طور پر اس کی رفتار تیز ہوگئی ہے۔ جدید ردی کی ٹوکری کی بازیابی کے الگورتھم بہت ترقی کرچکے ہیں ، اضافے سے جمع کرنے والے الگورتھم کو ردی کی ٹوکری کی بازیابی کے عمل کو ٹکڑے ٹکڑے کرنے کی اجازت دی جاسکتی ہے ، تاکہ پروگرام کے کام کو روکنے سے بچایا جاسکے۔ اور روایتی متحرک میموری کے انتظام کے الگورتھم میں بھی مناسب وقت پر میموری کو جمع کرنے کا کام کرنا چاہئے ، اور ردی کی ٹوکری کی بازیابی سے زیادہ فائدہ نہیں ہے۔
اور ردی کی ٹوکری کو دوبارہ حاصل کرنے کے الگورتھم کی بنیاد عام طور پر اسکین کرنے اور اس وقت استعمال ہونے والے تمام میموری بلاکس کو نشان زد کرنے پر مبنی ہوتی ہے ، جو کہ تمام مختص شدہ میموری سے غیر نشان زد میموری کو بازیافت کرتی ہے۔ C / C ++ میں ردی کی ٹوکری کو دوبارہ حاصل کرنے کا امکان نہیں ہے اس کا نظریہ عام طور پر اس بات پر مبنی ہوتا ہے کہ تمام میموری بلاکس کو صحیح طریقے سے اسکین نہیں کیا جاسکتا ہے جو استعمال ہوسکتے ہیں۔ تاہم ، جو کچھ ناممکن لگتا ہے وہ حقیقت میں اس پر عمل درآمد نہیں کرتا ہے۔ سب سے پہلے ، میموری کے اعداد و شمار کو اسکین کرکے ، متحرک طور پر اسٹاک پر مختص کردہ میموری میں اشارے کی نشاندہی کرنا آسان ہے۔ اگر شناخت کی غلطی ہو تو ، صرف کچھ غیر اشارے والے اعداد و شمار کو اشارہ کیا جاسکتا ہے ، اور اشارے کو غیر اشارے والے اعداد و شمار میں شامل نہیں کیا جاسکتا ہے۔ اس طرح ، ردی کی ٹوکری کو بازیافت کرنے کا عمل صرف غلطیوں کو بازیافت کرے گا اور اس کی صفائی نہیں کرے گا۔ اگر اس کے
کوڑا کرکٹ ری سائیکلنگ کے دوران ، صرف بی ایس ایس سیگمنٹ ، ڈیٹا سیگمنٹ ، اور فی الحال استعمال ہونے والی کوڑا کرکٹ کی جگہ کو اسکین کرنے کی ضرورت ہے ، جس میں متحرک میموری کے اشارے کی مقدار معلوم کی جاسکتی ہے۔ حوالہ شدہ میموری کو ریورس اسکین کرنے سے آپ کو فی الحال استعمال ہونے والی تمام متحرک میموری مل سکتی ہے۔
اگر آپ اپنے منصوبے کے لئے ایک اچھا ردی کی ٹوکری ری سائیکلنگ کو لاگو کرتے ہیں، تو میموری مینجمنٹ کی رفتار کو بہتر بنانے اور یہاں تک کہ مجموعی میموری کی کھپت کو کم کرنے کے لئے ممکن ہے. اگر آپ دلچسپی رکھتے ہیں تو، آپ کو ردی کی ٹوکری ری سائیکلنگ کے بارے میں آن لائن کاغذات اور لاگو شدہ لائبریریوں کو تلاش کر سکتے ہیں.
ٹویٹ ایمبیڈ کریںHK Zhang
#include<stdio.h>
int*fun(){
int k = 12;
return &k;
}
int main(){
int *p = fun();
printf("%d\n", *p);
getchar();
return 0;
}
نہ صرف قابل رسائی ، بلکہ قابل ترمیم بھی ، لیکن اس طرح کی رسائی غیر یقینی ہے مقامی متغیرات کے پتے پروگرام کے اپنے اسٹیک میں ہوتے ہیں، اتھارٹی متغیر کے ختم ہونے کے بعد، جب تک کہ اس مقامی متغیر کا میموری ایڈریس کسی اور متغیر کو نہ دیا جائے، اس کی قدر موجود ہے۔ لیکن اگر اس میں ترمیم کی گئی تو یہ نسبتاً خطرناک ہے، کیونکہ اس میموری ایڈریس نے پروگرام کے دیگر متغیرات کو دیا ہو سکتا ہے، اگر اشارے کے ذریعے زبردستی ترمیم کی گئی تو یہ پروگرام کو کریش کر سکتا ہے۔