View Full Version : چگونگی برگشت مقداری از نوع کلاس در توابع
یوسف زالی
یک شنبه 03 اردیبهشت 1391, 18:56 عصر
سلام.
فرض کنید تابعی داریم که مقداری از نوع TImage بر می گرداند.
اگر در کد زیر IMG قبلا ساخته شده باشد، memory leak خواهیم داشت؟ چرا؟
IMG := GetFromFunc(something); // sample
آیا اصلا نیازی هست که Result رو در توابع create کنیم؟
اصلا روال کلی این کار چیه؟
ممنون از همه دوستان.
Felony
دوشنبه 04 اردیبهشت 1391, 23:57 عصر
اگر در کد زیر IMG قبلا ساخته شده باشد، memory leak خواهیم داشت؟ چرا؟
AV خواهید داشت ؛ چون نوع برگشتی تابع شما یک شئ هست ، اون شئ هم ساخته نشده ( دقت کنید منظور Result هست ) ، کامپایلر به شیئی که برای Result به تابع اختصاص میدید ( IMG ) کاری نداری ، عملیاتی که داخل تابع انجام میشه باید روی یک شئ باشه یا نه ؟ اون شئ برای استفاده باید بهش روی حافظه فضا اختصاص پیدا کرده باشه یا نه ؟ آیا برای Result شما همچین اتفاقی افتاده ؟ شما دارید توی تابع از شیئی استفاده میکنید که فضایی بهش تخصیص داده نشده و AV طبیعی هست .
آیا اصلا نیازی هست که Result رو در توابع create کنیم؟
خیر ؛ این کار جالبی نیست و مدیریت کد و خطا ها و Memory Leak ها رو سخت میکنه ، همیشه بهتره این نوع اشیاء در Scope های محلی ساخته و آزاد بشن .
اصلا روال کلی این کار چیه؟
روال کار این هست که شئ رو بسازید و به عنوان پارامتر به تابعتون ارجاعش بدید تا تابع به صورت ارجاع به مرجع ( By Reference ) مستقیما با فضای اختصاص داده شده به شئ کار کنه و پس از پایان کار در هم تو همون کد فراخوان تابع شئ رو آزاد کنید ؛ مثلا :
procedure Test(SL: TStringList; Const MyStr: String);
begin
SL.Add(MyStr);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
aList: TStringList;
begin
aList := TStringList.Create;
try
Test(aList, 'This is sample text');
ShowMessage(aList.Strings[0]);
finally
aList.Free;
end;
end;
SAASTN
سه شنبه 05 اردیبهشت 1391, 15:22 عصر
اگر در کد زیر IMG قبلا ساخته شده باشد، memory leak خواهیم داشت؟ چرا؟
نمی دونم درست متوجه مطلب شدم یانه، اما اگر محتویات IMG بعد از فراخونی GetFromFunc تغییر کنه و دسترسی دیگه ای هم به مقدار قبلی IMG موجود نباشه، بله، نشت حافظه بوجود میاد. چون هیچ جا شیئی که قبلا آدرسش در IMG بوده آزاد نمی شه. کلا این که ما همه دسترسی های موجود به یک شیئ رو از دست بدیم اشتباهه، چون دلفی GarbageCollector نداره (البته غیر از Interface ها)، پس اگه شما خودت شیئی رو که ایجاد کردی آزاد نکنی اون حافظه تا پایان اجرای برنامه توسط سیستم عامل اشغال شده فرض میشه و این به معنیه نشت حافظه است.
آیا اصلا نیازی هست که Result رو در توابع create کنیم؟
در مورد کد حاضر که همونطور که آقای تاجیک گفتن اگه GetFromFunc خروجی رو ایجاد نکنه، در اولین بار استفاده از IMG خطای AV رخ میده. چون محتویات IMG به یه حافظه اختصاص یافته به شیئی اشاره نمی کنه.
اما این که اینکار باید انجام بشه یا نه و در کل همچین کاری درست هست یا نه به کاربرد بستگی داره. در کل بهتره هر کسی که وظیه ایجاد یه شیئ رو بر عهده داره، همون هم شیئ رو آزاد کنه. یعنی اگر یه تابع بصورت محلی به یه شیئ مصرفی احتیاج داره، خود تابع شیئ رو ایجاد کنه، عملیات مورد نظر رو روش انجام بده و در نهایت آزادش کنه. مثل مثالی که آقای تاجیک آوردن. اما یه وقت هست که یه تابع وظیفه آماده سازی و مقدار دهی اولیه یه شیئ رو برعهده داره. مثلا شیئ ایجاد میشه و درست بعد از اون ده تا از پراپرتی هاش ست میشه. واین چند خط کد هم به دفعات در برناممون تکرار میشه. بنابراین ما یه تابع ایجاد میکنیم که اون مشخصات اولیه رو به شیئ اختصاص بده. حالا این سوال مطرح میشه که چرا همون وظیفه ایجاد شیئ رو هم به اون تابع نسپریم و همه جا بجای دو خط کد یه خط فراخونی تابع ننویسیم. تو چنین شرایطی بهترین کار اینه که یه overload برای سازنده کلاسمون ایجاد کنیم، که همون پارامترها رو بگیره و بعد از ایجاد شیئ بهش اختصاص بده. اگر کلاس رو خودمون نوشته باشیم که مشکلی نیست و یه constructor جدید براش مینویسیم، اما اگه کلاس یه کلاس آماده باشه یا باید به روش آقای تاجیک عمل بشه یا یه کلاس جدید از اون کلاس مشتق بشه و constructor به کلاس جدید اضافه بشه. و در نهایت اگه به هر دلیلی (که در اکثر مواقع تنبلی خواهد بود) هیچ کدوم از این کار ها انجام نشه، به خاطر ابهامی که این کار داره، برای استفاده های بعدی خودمون یا دیگران این مورد حتما باید با کامنت در محل فراخونی تابع ذکر بشه و حتما بعد از استفاده شیئ Free بشه.
مورد دیگه ای که ممکنه خروجی تابع توی خود تابع ایجاد بشه بعضی از پراپرتی های کلاس ها هستند که ممکنه خیلی ازشون استفاده بشه یا در طول حیات شیئ هیچ وقت ازشون استفاده نشه. مثلا فرض کنید یه کلاس دارید که ممکنه در بعضی از مواقع جنبه ویژوال داشته باشه و روی فرم ترسیم بشه و بعضی مواقع اصلا روی فرم قرار نگیره و برای کاربرد دیگه ای ایجاد بشه (مثال قشنگتری به ذهنم نرسید!). خوب حالا ما یه پراپرتی Canvas داریم که ممکنه خیلی بهش احتیاج داشته باشیم و ممکنه اصلا بهش نیاز نداشته باشیم. در مواردی که اصلا بهش احتیاجی نداریم ایجاد کردن Canvas و اشغال حافظه توسط اون توجیحی نداره، و زمانی هم که زیاد بهش احتیاج داریم، ایجاد و آزاد سازی در هر بار استفاده سربار زیادی داره. تو چنین شرایطی معمولا بخش read پراپرتی Canvas رو بصورت تابع می نویسن و توی اون کنترل می کنن که اگه FCanvas قبلا ایجاد شده باشه، همون رو در خروجی قرار می دن و اگه قبلا ایجاد نشده باشه، اول ایجادش می کنن و بعد در خروجی قرارش میدن. اینجا وظیفه آزاد سازی Canvas هم بر عهده خود کلاس هست، یعنی خودش باید توی destructor فیلد FCanvas رو آزاد کنه. اینجا هم وظیف ایجاد و آزاد سازی و نگهداری دسترسی به شیئ بر عهده یک کلاس هست.
باز از موارد دیگه ای که میشه بهش اشاره کرد لیست ها هستن، همین مورد رو در تفاوت رفتار TList ها و TCollection ها هم می بینیم. TList تنها یک نگهدارنده با کاربرد عمومی هست، شما تنها میتونید یک شیئ رو به TList اضافه کنید یا ازش حذف کنید. به هر صورت TList نقشی در ایجاد و آزاد کردن عناصر خودش نداره. یعنی اگر شما TList رو Free کنید به معنی Free کردن عناصرش نیست و اگر دسترسی دیگه ای هم به اون عناصر نداشته باشیم با نشت حافظه روبرو خواهیم شد. در اصل کابرد اصلی TList همین نگهداری دسترسی به یه سری اشیاء برای استفاده های بعدی یا آزادسازی اونهاست. در مقابل TCollection کاربرد و رفتار دیگه ای داره، TCollection ها معمولا برای هر مورد استفاده مجزا بازنویسی میشن، عناصر موجود در اونها معمولا کاربردهای مشخص و به احتمال زیاد طول عمر یکسان دارند (همین خصوصیات رو در TImageList یا TActionList هم می بینیم). در این شرایط می بینیم که TCollection، علاوه بر نگهداری دسترسی به عناصر موجود در خودش، وظیفه ایجاد و آزادسازی اونها رو هم به عهده می گیره. یعنی TCollection در زمان آزاد سازی خودش، عناصرش رو هم آزاد می کنه. حالا به تفاوت توابع Add تو دو کلاس TList و TCollection میرسیم. توی TList روال Add شیئی رو که باید به لیست اضافه بشه بصورت پارامتر می گیره و تنها اون رو در ساختاری مثل لیست های پیوندی نگه میداره، اما Add تو TCollection یه تابعه که اول یه عنصر جدید ایجاد میکنه، اون عنصر رو به لیست خودش اضافه می کنه و در نهایت عنصر جدید رو در خروجی قرار میده. اینجا هم می بینیم که وظائف ایجاد، نگهداری دسترسی و آزاد سازی اشیائ تماما به یه کلاس واحد سپرده شده.
امیدوارم گویا بوده باشه
یوسف زالی
سه شنبه 05 اردیبهشت 1391, 16:11 عصر
به به!
عالی بود.
از دوستان خوبم ممنون که توضیحاتشون جامع و کامل بود.
در کل اگر ممکنه کمی در رابطه با آزاد کردن اشیا توضیح کوتاهی هم بفرمایید فکر می کنم مطلب به طور کامل ادا بشه.
مثلا اشیایی که parent اونها فرم هست اما ران تایم هستند، یا در فرم دیگری هستند..
در کل درختی که destroy روی اون اتفاق می افته با parent منشعب می شه یا با owner?
اگر با parent هست پس اصلا فلسفه وجودی owner چیه؟
البته خود من کمی در موردش تحقیق محلی و آزمون و خطا داشتم اما با تجربیات دوستان مطمئن می شم.
باز هم تشکر
SAASTN
سه شنبه 05 اردیبهشت 1391, 23:04 عصر
در کل اگر ممکنه کمی در رابطه با آزاد کردن اشیا توضیح کوتاهی هم بفرمایید فکر می کنم مطلب به طور کامل ادا بشه.
اگر منظور موارد مربوط به مدیریت حافظه دلفی در زمان آزادسازی اشیاء هست من اطلاعی ندارم، تنها چیزی که اونم بصورت کلی می دونم اینه که وقتی شما یک شیئ رو ایجاد می کنید حافظه به میزان مورد نیاز برای کلاس Allocate میشه و آدرس شروع اون بلاک حافظه در خروجی Constructor قرار می گیره. در واقع یه متغیر از نوع کلاس (TObject و بر و بچه هاش) تفاوت حافظه ای با یه Pointer نداره. در زمان آزادسازی هم میزان حافظه اشغال شده توسط شیئ که آدرس شروعش در متغیریه که تخریبش کردیم آزاد میشه. دیگه از جزئیاتش و مسائل تکنیکیش خبر ندارم. ولی یادمه آقای کشاورز یه بار (البته یه بارش رو من دیدم) توضیحاتی در این مورد دادن.
در کل درختی که destroy روی اون اتفاق می افته با parent منشعب می شه یا با owner?
اگر با parent هست پس اصلا فلسفه وجودی owner چیه؟
در واقع با هیچ کدوم، در عمل اون آزاد سازی با لیست Components یک کامپوننت اتفاق میافته و البته این لیست یک جهت از یه ارتباط دوطرفه است و جهت دیگه این ارتباط از طریق Owner برقرار میشه (پس به نوعی میشه گفت جواب اون سوال Owner هست نه Parent). در واقع پراپرتی Owner مربوط به TComponent هست، یعنی تمامی کامپوننت ها، اعم از بصری و غیر بصری یه Owner دارن. وقتی Owner یک کامپوننت ست میشه، کامپوننتی که به عنوان صاحب معرفی شده اون یکی رو به لیست کامپوننت های خودش اضافه می کنه. از طرفی کامپوننت ها در زمان آزاد سازی، تمامی عناصر موجود در لیست کامپوننت های خودشون رو هم آزاد می کنن. همونطور که می بینید این مورد خلاف اون اصلی هست که در پست قبل گفتم:بامزه:، یعنی کامپوننت توسط کلاس دیگه ای (مثلا TForm1) ایجاد میشه، ولی توسط کلاس دیگه ای آزاد میشه (TComponent). پس باید توجه داشته باشیم که اگر Owner کامپوننت هایی که در زمان اجرا ایجاد می کنیم رو برابر nil قرار بدیم، این کامپوننت ها در زمان آزادسازی فرمی که روشون قرار دارن آزاد نمیشن و ما باید برای آزادسازیشون حتما یه دسترسی نگه داریم، ولی اگر Owner اونها رو برابر یکی از کامپوننت های روی فرم قرار بدیم، اون کامپوننت حتما به همرا فرم آزاد میشه. اگر هم شک داشته باشیم که ممکنه در زنجیره Ownership پارگی بوجود اومده باشه، می تونیم خود فرم رو به عنوان Owner معرفی کنیم، اگر به کدها و مثالها دقت کنی می بینی که در اکثر موارد خود فرم به عنوان صاحب معرفی میشه.
از طرف دیگه Parent یکی از مشخصات TControl هست (که خودش از کامپوننت مشتق شده)، یعنی تنها کامپوننت های بصری این مشخصه رو دارن. کارکردش هم نشون دادن محل نمایش کنترل هست. یعنی اگه شما پدر یه کنترل رو برابر فرم قرار بدید اون کنترل در محدوده Client فرم نمایش پیدا می کنه و اگه پدر یا والدشو برابر Panel1 قرار بدین تنها در محدوده کلاینت پنل نمایش پیدا می کنه. این مورد هم هیچ ربطی به آزاد سازی نداره. پس اگه ما یه کنترل ایجاد کنیم و Ownerش رو nil کنیم و Parentش رو برابر Form1 قرار بدیم، اون کنترل با آزاد سازی Form1 آزاد نخواهد شد و ما خودمون باید آزادش کنیم.
یخورده درهم برهم شد! دیگه شرمنده بهتر از این بلد نیستم بگم.
یوسف زالی
چهارشنبه 06 اردیبهشت 1391, 14:10 عصر
آقا عالی بود. خسته نباشی. ممنون.
من به جوابم رسیدم.
SAASTN
سه شنبه 26 اردیبهشت 1391, 10:40 صبح
پس اگه ما یه کنترل ایجاد کنیم و Ownerش رو nil کنیم و Parentش رو برابر Form1 قرار بدیم، اون کنترل با آزاد سازی Form1 آزاد نخواهد شد و ما خودمون باید آزادش کنیم.
بعد از صحبت هایی که اینجا (http://barnamenevis.org/showthread.php?341384-%D9%85%D8%B4%DA%A9%D9%84-%D8%AE%D8%A7%D8%B1%D8%AC-%D9%86%D8%B4%D8%AF%D9%86-Frame-%D8%A7%D8%B2-%D8%AD%D8%A7%D9%81%D8%B8%D9%87-%D9%BE%D8%B3-%D8%A7%D8%B2-%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-%D8%A2%D9%86-%D8%AF%D8%B1-%D8%B2%D9%85%D8%A7%D9%86-%D8%A7%D8%AC%D8%B1%D8%A7) مطرح شد یه بررسی کردم و دیدم که TWinControl (که از TControl مشتق شده) موقع آزادسازی خودش لیست کنترلهای خودش رو آزاد می کنه. بنابر این جمله بالا در مورد TWinControl ها و کلاسهای مشتق شده از اون صادق نیست، پس اگه یه TEdit یا TButton ایجاد کنیم و تنها Parent اونها رو برابر Form1 قرار بدیم، اون کنترلها همراه با Form1 آزاد میشن و نیازی نیست حتما خودمون آزادشون کنیم.
vBulletin® v4.2.5, Copyright ©2000-1404, Jelsoft Enterprises Ltd.