در علوم کامپیوتر ، ارسال پویا فرآیند انتخاب اجرای یک عملیات چندشکلی ( روش یا تابع) است که در زمان اجرا فراخوانی شود . معمولاً در زبانها و سیستمهای برنامهنویسی شیگرا (OOP) استفاده میشود و مشخصه اصلی آن در نظر گرفته میشود . [1]
سیستم های شی گرا یک مشکل را به عنوان مجموعه ای از اشیاء متقابل مدل می کنند که عملیاتی را انجام می دهند که با نام ذکر شده است. چند شکلی پدیده ای است که در آن اشیاء تا حدی قابل تعویض، هر یک عملیاتی با نام مشابه اما احتمالاً در رفتار متفاوت را نشان می دهند. به عنوان مثال، یک شی File و یک شی پایگاه داده هر دو دارای یک روش StoreRecord هستند که می تواند برای نوشتن یک رکورد پرسنل در ذخیره سازی استفاده شود. پیاده سازی آنها متفاوت است. یک برنامه یک مرجع به یک شی دارد که ممکن است یک شی File یا یک شی پایگاه داده باشد . این که کدام است ممکن است با تنظیم زمان اجرا مشخص شده باشد، و در این مرحله، برنامه ممکن است نداند یا اهمیتی نداشته باشد. هنگامی که برنامه StoreRecord را روی شیء فراخوانی می کند، چیزی باید انتخاب کند که کدام رفتار اعمال می شود. اگر کسی OOP را به عنوان ارسال پیام به اشیا در نظر بگیرد، در این مثال، برنامه یک پیام StoreRecord را به یک شی از نوع ناشناخته ارسال می کند و آن را به سیستم پشتیبانی زمان اجرا می گذارد تا پیام را به شی مناسب ارسال کند. شیء هر رفتاری را که اجرا می کند اعمال می کند. [2]
ارسال پویا در تضاد با ارسال استاتیک است که در آن اجرای یک عملیات چند شکلی در زمان کامپایل انتخاب می شود . هدف از ارسال پویا به تعویق انداختن انتخاب یک پیاده سازی مناسب تا زمانی است که نوع زمان اجرا یک پارامتر (یا پارامترهای متعدد) مشخص شود.
ارسال دینامیک با اتصال دیرهنگام (همچنین به عنوان اتصال پویا شناخته می شود) متفاوت است . Binding Name یک نام را با یک عملیات مرتبط می کند. یک عملیات چند شکلی چندین اجرا دارد که همگی با یک نام مرتبط هستند. صحافی ها را می توان در زمان کامپایل یا (با صحافی دیرهنگام) در زمان اجرا انجام داد. با ارسال پویا، یک پیاده سازی خاص از یک عملیات در زمان اجرا انتخاب می شود. در حالی که ارسال دینامیک به معنای اتصال دیرهنگام نیست، اتصال دیرهنگام مستلزم ارسال پویا است، زیرا اجرای عملیات با کران دیر تا زمان اجرا مشخص نیست. [ نیازمند منبع ]
انتخاب نسخه ای از یک روش برای فراخوانی ممکن است بر اساس یک شی واحد یا ترکیبی از اشیاء باشد. اولی تک ارسال نامیده می شود و مستقیماً توسط زبان های شی گرا رایج مانند Smalltalk ، C++ ، Java ، C# ، Objective-C ، Swift ، JavaScript و Python پشتیبانی می شود . در این زبانها و زبانهای مشابه، میتوان روشی را برای تقسیم با نحوی فراخوانی کرد
سود سهام . تقسیم ( تقسیم کننده ) # سود سهام / تقسیم کننده
که در آن پارامترها اختیاری هستند. این به عنوان ارسال پیامی به نام divide با مقسومکننده پارامتر به سود در نظر گرفته میشود . یک پیاده سازی تنها بر اساس نوع سود تقسیمی (شاید منطقی ، ممیز شناور ، ماتریس )، بدون توجه به نوع یا مقدار مقسوم علیه انتخاب خواهد شد .
در مقابل، برخی از زبان ها روش ها یا توابع را بر اساس ترکیب عملوندها ارسال می کنند. در مورد تقسیم، انواع تقسیم و تقسیم کننده با هم تعیین می کنند که کدام عملیات تقسیم انجام می شود. این به عنوان ارسال چندگانه شناخته می شود . نمونههایی از زبانهایی که از ارسال چندگانه پشتیبانی میکنند عبارتند از Common Lisp ، Dylan و Julia .
یک زبان ممکن است با مکانیسمهای مختلف ارسال پویا پیادهسازی شود. انتخابهای مکانیزم ارسال پویا که توسط یک زبان ارائه میشود تا حد زیادی پارادایمهای برنامهنویسی را که در دسترس هستند یا برای استفاده در یک زبان خاص طبیعیتر هستند، تغییر میدهند.
به طور معمول، در یک زبان تایپ شده، مکانیسم ارسال بر اساس نوع آرگومان ها (که بیشتر بر اساس نوع گیرنده پیام است) انجام می شود. زبانهایی با سیستمهای تایپ ضعیف یا بدون سیستم، اغلب یک جدول اعزام را به عنوان بخشی از دادههای شی برای هر شیء حمل میکنند. این به رفتار نمونه اجازه می دهد زیرا هر نمونه ممکن است یک پیام داده شده را به یک روش جداگانه ترسیم کند.
برخی از زبان ها یک رویکرد ترکیبی ارائه می دهند.
ارسال پویا همیشه هزینهای را به همراه خواهد داشت، بنابراین برخی از زبانها ارسال ثابت را برای روشهای خاص ارائه میدهند.
C++ از اتصال اولیه استفاده می کند و ارسال پویا و استاتیک را ارائه می دهد. فرم پیش فرض ارسال ثابت است. برای به دست آوردن ارسال پویا، برنامه نویس باید یک متد را مجازی اعلام کند .
کامپایلرهای C++ معمولاً توزیع پویا را با ساختار داده ای به نام جدول تابع مجازی (vtable) پیاده سازی می کنند که نگاشت نام به پیاده سازی را برای یک کلاس معین به عنوان مجموعه ای از اشاره گرهای تابع عضو تعریف می کند. این صرفاً یک جزئیات پیاده سازی است، زیرا در مشخصات C++ به vtable ها اشاره نشده است. نمونههایی از این نوع، یک اشارهگر را به این جدول به عنوان بخشی از دادههای نمونه خود ذخیره میکنند، که در صورت استفاده از وراثت چندگانه ، سناریوها را پیچیده میکند. از آنجایی که C++ از اتصال دیرهنگام پشتیبانی نمی کند، جدول مجازی در یک شی C++ را نمی توان در زمان اجرا تغییر داد، که مجموعه بالقوه اهداف ارسال را به یک مجموعه محدود انتخاب شده در زمان کامپایل محدود می کند.
بارگذاری بیش از حد نوع باعث ارسال پویا در ++C نمی شود زیرا زبان انواع پارامترهای پیام را بخشی از نام پیام رسمی در نظر می گیرد. این بدان معنی است که نام پیامی که برنامه نویس می بیند، نام رسمی مورد استفاده برای اتصال نیست.
در Go ، Rust و Nim ، از تنوع متنوع تری از اتصال اولیه استفاده می شود. اشاره گرهای Vtable با ارجاعات شیء به عنوان «نشانگرهای چربی» («رابط» در Go، یا «اشیاء صفت» در Rust [3] [4] ) حمل می شوند.
این رابط های پشتیبانی شده را از ساختارهای داده زیرین جدا می کند. هر کتابخانه کامپایل شده برای استفاده صحیح از یک نوع نیازی به دانستن طیف کامل رابط های پشتیبانی شده ندارد، فقط طرح بندی vtable خاصی را که نیاز دارد. کد می تواند رابط های مختلف را به یک قطعه از داده ها به توابع مختلف منتقل کند. این تطبیق پذیری به هزینه داده های اضافی با هر مرجع شی است، که اگر بسیاری از چنین مراجع به طور مداوم ذخیره شوند مشکل ساز است.
اصطلاح اشاره گر چربی به سادگی به یک اشاره گر با اطلاعات مرتبط اضافی اشاره دارد. اطلاعات اضافی ممکن است یک نشانگر vtable برای ارسال پویا باشد که در بالا توضیح داده شد، اما معمولاً اندازه شی مرتبط برای توصیف یک قطعه است . [ نیازمند منبع ]
Smalltalk از یک توزیع کننده پیام مبتنی بر نوع استفاده می کند. هر نمونه دارای یک نوع واحد است که تعریف آن شامل متدها است. هنگامی که یک نمونه پیامی را دریافت می کند، توزیع کننده روش مربوطه را در نقشه پیام به روش برای نوع جستجو می کند و سپس متد را فراخوانی می کند.
از آنجایی که یک نوع می تواند زنجیره ای از انواع پایه داشته باشد، این جستجو می تواند گران باشد. به نظر می رسد اجرای ساده مکانیزم Smalltalk سربار به طور قابل توجهی بالاتر از C++ داشته باشد و این سربار برای هر پیامی که یک شی دریافت می کند متحمل می شود.
پیادهسازیهای Real Smalltalk اغلب از تکنیکی به نام کش درونی [5] استفاده میکنند که ارسال روش را بسیار سریع میکند. کش درونی اساساً آدرس روش مقصد قبلی و کلاس شی سایت تماس (یا چندین جفت برای کش چند طرفه) را ذخیره می کند. روش ذخیرهسازی شده با متداولترین روش هدف (یا فقط کنترلکننده خطای حافظه پنهان)، بر اساس انتخابگر روش، مقداردهی اولیه میشود. هنگامی که در حین اجرا به سایت فراخوانی متد رسید، فقط آدرس موجود در حافظه پنهان را فراخوانی می کند. (در یک مولد کد پویا، این فراخوانی یک تماس مستقیم است، زیرا آدرس مستقیم توسط منطق از دست دادن حافظه پنهان وصله شده است.) کد پیش درآمد در متد فراخوانی شده، کلاس ذخیره شده در حافظه پنهان را با کلاس شی واقعی مقایسه می کند، و اگر آنها مطابقت ندارند ، برای یافتن متد صحیح در کلاس، به یک کنترل کننده خطای حافظه پنهان منشعب می شود. یک پیادهسازی سریع ممکن است چندین ورودی حافظه پنهان داشته باشد و اغلب تنها به چند دستورالعمل نیاز دارد تا در یک خطای کش اولیه اجرا به روش صحیح برسد. مورد رایج یک تطبیق کلاس حافظه پنهان خواهد بود و اجرا فقط در متد ادامه خواهد داشت.
کش کردن خارج از خط نیز می تواند در منطق فراخوانی متد با استفاده از کلاس شی و انتخابگر متد استفاده شود. در یک طراحی، انتخابگر کلاس و متد هش شده و به عنوان یک شاخص در جدول کش ارسال متد استفاده می شود.
از آنجایی که Smalltalk یک زبان انعکاسی است، بسیاری از پیادهسازیها اجازه میدهند تا اشیاء منفرد را به اشیا با جداول جستجوی متد تولید شده به صورت پویا تغییر دهند. این اجازه می دهد تا رفتار شی را بر اساس هر شی تغییر دهید. دسته کاملی از زبانها که به عنوان زبانهای مبتنی بر نمونه اولیه شناخته میشوند ، از این موضوع رشد کردهاند که معروفترین آنها Self و JavaScript هستند . طراحی دقیق ذخیرهسازی متدهای ارسالی، حتی به زبانهای مبتنی بر نمونه اولیه اجازه میدهد تا روش ارسال با کارایی بالا داشته باشند.
بسیاری دیگر از زبان های تایپ پویا، از جمله Python ، Ruby ، Objective-C و Groovy از رویکردهای مشابه استفاده می کنند.
class Cat : def speak ( self ): چاپ ( "Meow" )class Dog : def speak ( self ): چاپ ( "Woof" )def speak ( pet ): # به صورت پویا روش صحبت را ارسال می کند # pet می تواند نمونه ای از گربه یا سگ حیوان خانگی باشد . صحبت کن ()گربه = گربه () صحبت می کند ( گربه ) سگ = سگ () صحبت می کند ( سگ )
#include <iostream> // پت را یک کلاس پایه مجازی انتزاعی کنید کلاس Pet { public : virtual void speak () = 0 ; }; class Dog : public Pet { public : void speak () override { std :: cout << "Woof! \n " ; } } class Cat : public Pet { public : void speak () override { std :: cout << "Meow! \n " ; } } // speak() قادر خواهد بود هر چیزی را که از Pet void speak ( Pet & pet ) نشأت می گیرد بپذیرد . صحبت کردن (); } int main () { Dog fido ; گربه سیمبا ؛ صحبت کردن ( fido ); صحبت کردن ( simba )؛ بازگشت 0 ; }
اشیاء صفت ارسال پویا را انجام می دهند […] وقتی از اشیاء صفت استفاده می کنیم، Rust باید از ارسال پویا استفاده کند. کامپایلر همه انواعی را که ممکن است با کدی که از اشیاء صفت استفاده میکند، نمیداند، بنابراین نمیداند کدام متد بر روی کدام نوع اجرا شده است. در عوض، در زمان اجرا، Rust از نشانگرهای داخل شیء صفت استفاده میکند تا بداند کدام متد را فراخوانی کند. این جستجو هزینه زمان اجرا را متحمل می شود که با ارسال استاتیک رخ نمی دهد. ارسال پویا همچنین مانع از انتخاب کامپایلر میشود تا کد یک متد را درون خطی انتخاب کند، که به نوبه خود از برخی بهینهسازیها جلوگیری میکند.(xxix+1+527+3 صفحه)
دلیل اینکه Geos به 16 وقفه نیاز دارد این است که این طرح برای تبدیل فراخوانی های تابع بین بخش ("فاصل") به وقفه، بدون تغییر اندازه کد استفاده می شود. دلیل انجام این کار به این دلیل است که "چیزی" (هسته) بتواند خود را به هر فراخوانی بین بخش که توسط برنامه Geos انجام می شود متصل کند و مطمئن شود که بخش های کد مناسب از حافظه مجازی بارگیری شده و قفل شده اند. در اصطلاح DOS ، این می تواند با یک بارگذار همپوشانی قابل مقایسه باشد ، اما می تواند بدون نیاز به پشتیبانی صریح از کامپایلر یا برنامه اضافه شود. چیزی شبیه به این است: […] 1. کامپایلر حالت واقعی دستورالعملی مانند این تولید می کند: CALL <segment>:<offset> -> 9A <offflow><offhigh><seglow><seghigh> با <seglow>< seghigh> معمولاً به عنوان آدرسی تعریف می شود که باید در زمان بارگذاری بسته به آدرسی که کد در آن قرار داده شده است، ثابت شود. […] 2. پیوند دهنده Geos این را به چیز دیگری تبدیل می کند: INT 8xh -> CD 8x […] DB <seghigh>,<offflow>,<offhigh> […] توجه داشته باشید که این دوباره پنج بایت است، بنابراین می توان ثابت "در جای خود". اکنون مشکل این است که یک وقفه به دو بایت نیاز دارد، در حالی که یک دستورالعمل CALL FAR فقط به یک بایت نیاز دارد. در نتیجه، بردار 32 بیتی (<seg><ofs>) باید به 24 بیت فشرده شود. [...] این با دو چیز به دست میآید: اول، آدرس <seg> بهعنوان یک «دسته» برای قطعهای کدگذاری میشود که پایینترین نوک آن همیشه صفر است. این چهار بیت را ذخیره می کند. علاوه بر این […] چهار بیت باقیمانده به قسمت پایین بردار وقفه می روند، بنابراین چیزی از INT 80h تا 8Fh ایجاد می کنند. [...] کنترل کننده وقفه برای همه آن بردارها یکسان است. آدرس را از نماد سه و نیم بایتی "بازگشایی" می کند، آدرس مطلق بخش را جستجو می کند و پس از بارگیری حافظه مجازی، تماس را فوروارد می کند... بازگشت از تماس نیز انجام می شود. از طریق کد باز کردن قفل مربوطه عبور کنید. […] نوک پایین بردار وقفه (80h-8Fh) بیت 4 تا 7 دسته قطعه را نگه می دارد. بیت 0 تا 3 یک دسته سگمنت (طبق تعریف یک هندل Geos) همیشه 0 است. […] همه Geos API از طریق طرح «همپوشانی» اجرا می شوند […]: زمانی که یک برنامه Geos در حافظه بارگذاری می شود، لودر به طور خودکار انجام می شود. فراخوانی توابع موجود در کتابخانه های سیستم را با تماس های مبتنی بر INT مربوطه جایگزین کنید. به هر حال، اینها ثابت نیستند، اما به دسته اختصاص داده شده به بخش کد کتابخانه بستگی دارند.[…] Geos در ابتدا قرار بود خیلی زود در […]، با حالت واقعی ، به حالت محافظت شده تبدیل شودتنها یک "گزینه میراث" است […] تقریباً هر خط کد اسمبلی برای آن آماده است […]
در مورد چنین نشانگرهای مخدوش […] سال ها پیش، اکسل و من به روشی فکر می کردیم که چگونه از *یک* نقطه ورودی به یک درایور برای بردارهای وقفه های متعدد استفاده کنیم (زیرا این باعث می شود فضای زیادی برای ما ذخیره کنیم. چندین نقطه ورودی و کد فریم راهاندازی/خروج کمابیش یکسان در همه آنها)، و سپس به کنترلکنندههای مختلف وقفه در داخل بروید. برای مثال: 1234h:0000h […] 1233h:0010h […] 1232h:0020h […] 1231h:0030h […] 1230h:0040h […] همه دقیقاً به همان نقطه ورودی اشاره می کنند. اگر INT 21h را به 1234h:0000h و INT 2Fh را به 1233h:0010h متصل کنید، و به همین ترتیب، همه آنها از همان "خلاف" عبور می کنند، اما شما همچنان می توانید بین آنها تمایز قائل شوید و در داخل به کنترل کننده های مختلف منشعب شوید. به یک نقطه ورودی "فشرده" در یک خرد A20 برای بارگذاری HMA فکر کنید. این تا زمانی کار می کند که هیچ برنامه ای شروع به انجام بخش: جادوهای افست نکند. [...] این را با رویکرد مخالف برای داشتن چندین نقطه ورودی مقایسه کنید (شاید حتی از پروتکل اشتراک گذاری وقفه IBM پشتیبانی می کند ) ، که اگر وقفه های زیادی را قلاب کنید، حافظه بسیار بیشتری مصرف می کند. […] ما به این نتیجه رسیدیم که به احتمال زیاد در عمل ذخیره نمی شود زیرا شما هرگز نمی دانید که آیا سایر درایورها به چه دلایلی نشانگرها را عادی یا غیرعادی می کنند. […](نکته. چیزی شبیه به « نشانگرهای چربی » مخصوصاً برای بخش حالت واقعی اینتل : آدرسدهی افست در پردازندههای x86 ، شامل یک اشارهگر عمداً غیرعادیشده به یک نقطه ورودی کد مشترک و برخی اطلاعات برای تشخیص تماسگیرندگان مختلف در کد مشترک کد در حالی که در یک سیستم باز ، نمونه های شخص ثالث (در سایر درایورها یا برنامه های کاربردی) را نمی توان به طور کامل رد کرد ، این طرح را می توان با خیال راحت در رابط های داخلی استفاده کرد تا از توالی کد ورودی اضافی جلوگیری شود.