10. 01. void use(Container& c)
02. {
03. const int sz = c.size();
04. for (int i = 0; i != sz; ++i)
05. cout << c[i] << 'n';
06. }
Editor's Notes
در این ویدیو ادامه ی بحث مکانیزم های Abstraction رو پی می گیریم و در مورد Abstract typeها صحبت خواهیم کرد و با استفاده از مفهوم Container چند مثال می بینیم و بعد در مورد پیاده سازی virtual functionها در کامپایلر یه اشاره ای می کنیم.
کلاسهایی مثل complex و vector که در ویدیوی قبلی بررسی کردیم کلاسهای Concrete بودن.کلاسهایی که Representation اونا قسمتی از definition اونا باشه رو concrete میگیم و رفتار اونا شبیه built-in typeها هست. ولی در Abstract typeها بر خلاف concrete typeها نحوه ی نمایش کلاس رو از خود کلاس جدا میکن و به طور کامل کاربر رو از دونستن جزئیات پیاده سازی بی نیاز می کن.
برای اینکه ما بتونیم یک Abstract type بسازیم باید Interface رو از Representation کلاس جدا کنیم و به اصطلاح decouple کنیم و تمام memberهای اون رو باید از داخل کلاس خارج کنیم. این کار باعث میشه ما اطلاعاتی از representation کلاس نداشته باشیم و حتی size اون رو هم نمی دونیم.
پس باید در حافظه ی free store یا Heap ساخته بشن و از طریق pointer به اونا دسترسی داشته باشیم.
برای مثال ما یک interface برای vector تعریف می کنیم و یک کلاس Container به صورت Abstract تعریف می کنیم. این کلاس یک pure interface هست که برای این نوشته می شه که بگیم که definition اون رو بعدا انجام خواهیم داد.
کلمه ی Virtual به معناست که توابعی که جلوش نوشته شده بعدا در کلاسی که از این interface مشتق میشه پیاده سازی خواهد شد.
توابعی که اولش virtual میذاریم virtual function گفته میشه. کلاسی که از کلاس Container مشتق میشه باید پیاده سازی مناسب رو برای واسط های Container فراهم کنه در غیر این صورت کامپایلر error خواهد داد.
این = 0 که در انتها نوشته شده به این معناست که تابعی که declare شده به صورت pure virtual هست و در کلاس مشتق پیاده سازی خواهد شد.
با تعریف کلاس Container شما نمی توندی یک Object از کلاس Container بسازید و فقط یک Interface برای استفاده ارائه میده و کلاس هایی که از Interface مشتق میشن باید اپراتور subscript و size رو پیاده سازی کنند. کلاسی که یک یا چند تا pure virtual function داشته باشد کلاس abstract گفته میشه.
از Container میشه به این صورت استفاده کرد. تابع use از واسطی که برای Container تعریف کردیم استفاده می کنه و بطور کامل از جزئیات پیاده سازی بی اطلاع هست و اصلا خبر نداره که چطور پیاده سازی شده. کلاسی که یک interface برای کلاس های دیگه تعریف می کنه در اصطلاح polymorphic یا چند ریختی گفته میشه. کلاس Container دارای Constructor نیست چون هیچ memberی نداره که مقدار دهی اولیه بکنه. اما باید دارای Destructor از نوع virtual باشه.
بخاطر اینکه کلاسهای Abstract همیشه از طریق pointer یا reference مورد استفاده قرار می گیرند و کسی که داره اون Pointer رو Destruct میکنه اطلاعی از resourceهایی که اون پیاده سازی خاص برای خودش Allocate کرده خبر نداره.
تابع use میتونه از توابعی که برای کلاس Container تعریف شدن استفاده کنه.
برای اینکه بتونید interface رو پیاده سازی کنید باید یک کلاس Concrete ایجاد کنید که از کلاس pure interface مشتق شده باشه.
با نوشتن :public بعد از اسم کلاس مشخص می کنیم که از چه Interfaceیی می خوایم مشتق ایجاد کنیم. در اصطلاح می گیم که کلاس Vector_container از Container مشتق شده یا کلاس یک Subtype از Container هست.یا به Vector_container می گیم Drived class یا subclass و به Containerمیگیم base class یا superclass. کلاس مشتق تمامی memberهای کلاس base رو به ارث می بره و به این موضوع در اصطلاح Inheritance یا ارث بری گفته میشه.
نوشتن پیاده سازی برای اپراتور subscript و size در کلاس Drived باعث میشه که اپراتور subscript و size در کلاس Base به اصطلاح Override بشن. Destructor مربوط به Vector_container هم باعث override شدن Destructor در Container میشه و با فراخوانی Destructor کلاس Drived به صورت ضمنی Destructor کلاس base هم فراخوانی خواهد شد.
در پیاده سازی کلاس Vector_container ما از Vectorیی استفاده کردیم که در ویدیوهای قبلی دیده بودیم. سطح دسترسی توی کلاس به صورت پیش فرض private هست پس وکتور v فقط داخل متدهای کلاس قابل دسترسی خواهد بود ولی خط 4 با نوشتن public: بقیه اعضای کلاس رو public تعریف کردیم.
برای استفاده از کلاس Vector_container هم می تونیم به راحتی یک instance از Vector بسازیم و به تابع use بدیم. تابع use چون از interface کلاس container اطلاع داره می تونه بدون دونستن اینکه چه پیاده سازی انجام شده از اون استفاده کنه.
حالا بیاید یک پیاده سازی دیگه برای کلاس Container بنویسم که بجای اینکه از Vector استفاده کنه از list استفاده کنه.
Representation کلاس List_container رو نسبت به Vector_container عوض می کنیم و از std::list استفاده می کنیم.
کلاس List_container دارای default constructor و initialiser-list constructor هست و یه destructor هم داره. توی تابع size هم اندازه ی لیست رو برگردونده. اما پیاده سازی اپراتور subscript رو در اسلاید بعدی با هم می بینیم.
چون ما از std::list استفاده کردیم برای پیاده سازی اپراتور subscript مجبوریم از for استفاده کنیم.
همین جا یه نوع جدیدی از For رو هم معرفی می کنیم. به این نوع for می گیم range for که از C++11 اضافه شده و از range for برای پیمایش Containerها استفاده میشه.
از کلمه ی کلیدی auto برای این استفاده میشه که type متغییر رو ننویسیم و کامپایلر خودش type مناسب رو پیدا می کنه و جای auto قرار میده. Range for به این صورت عمل می کنه که یک متغییر به نام x تعریف می کنیم که از جنس المانهای داخل Container هست. بعد یه دو نقطه میذاریم و بعد اون Containerیی که می خوایم پیمایش کنیم رو می نویسیم.
نحوه ی استفاده از List_container هم مشابه Vector_container هست. با یک initializer-list مقدار اولیه دادیم و بعد به عنوان ورودی به تابع Use داده میشه. و تابع Use هم چون از interface کلاس Container خبر داره می تونه ازش استفاده کنه. تابع use براش فرقی نداره که ورودی اون از نوع list_container باشه یا vector_container باشه فقط باید interface مربوط به Container رو باید پیاده سازی کرده باشه. تا زمانی که ورودی تابع pointer یا reference به یک Interface باشه میشه هر پیاده سازی رو به تابع داد و تابع از روی Interface میتونه از اون ورودی استفاده کنه.
یه بار دیگه تابع use رو با هم ببینیم. سوالی این جا مطرح میشه که تابع Use چطور میتونه تابع درست رو پیدا کنه و اجرا کنه؟ چطور اپراتور subscript مناسب رو پیدا میکنه؟
جواب اینه که برای پیدا کردن virtual function درست، کامپایلر باید یک سری اطلاعات اضافه در مورد Drived کلاس هایی که از Container به ارث بردند رو نگه داری کنه تا با استفاده از اونا در زمان اجرا، تابع درست رو انتخاب کنه و اجرا کنه.
یک راه اینه که کامپایلر نام توابع virtual رو به index تبدیل کنه و یک جدول از Pointerها به virtual functionها درست کنه. به این جدول Virtual Table یا به اختصار vtable می گیم. هر کلاسی که توابع virtual داره یک vtable مختص خودش رو داره. به صورت گرافیکی می بینید.
کامپایلر با داشتن این جدول می تونه تابع مناسب رو با پیدا کردن Index اون تابع و یافتن function pointer مربوطه پیدا کنه و با دادن پارامترهای ورودی اون تابع رو اجرا کنه. با این مکانیزم تابع use میتونه بدون دونستن اینکه container چه طور پیاده سازی شده، تابع virtual مناسب رو call کنه.
در این ویدیو یه تعریفی از Abstract typeها گفتیم و مفهوم Container رو تعریف کردیم و یه Interface برای Container نوشتیم و 2 تا پیاده سازی مختلف از این interface رو باهم دیدیم. در آخر در مورد virtual function ها و vtable بحث کردیم.