Кіріспе

Бағдарламалауда, объектіні жою кезінде орындалатын арнайы әдіс, видео ойындар және оптикалық дисктермен байланысты процесс. Компьютерлік ғылымда финализер немесе finalize әдісі – бұл аяқталуды орындайтын арнайы әдіс, әдетте тазалаудың бір түрі. Финализер объектіні жою кезінде, объекті бөлінуден бұрын орындалады және объекті құрылу кезінде, бөлінуден кейін орындалатын инициализаторға толықтыру болып табылады. Кейбір мамандар оларды дұрыс пайдаланудың қиындығы мен күрделілігіне байланысты қолданудан аулақ болуға кеңес береді, ал оның орнына альтернативтер ұсынылады, негізінен жою үлгісі (финализерлердің проблемаларын қараңыз). Финализер термині көбінесе қоқыс жинауды қолданатын объектіге бағытталған және функционалдық бағдарламалау тілдерінде қолданылады, оның классикалық үлгісі Smalltalk. Бұл деструктордан өзгеше, ол детерминистік өмір сүру циклы бар тілдерде, әдетте C++ тілінде, аяқтау үшін шақырылатын әдіс. Бұл әдетте өзгеше: тілде финализер (егер қоқыс автоматты түрде жиналса) немесе деструктор (егер жад қолмен басқарылса) болады, бірақ сирек жағдайларда тілде екеуі де болуы мүмкін, мысалы C++/CLI және D тілдерінде, ал сілтемелерді санау (қоқыс жинауды іздеудің орнына) жағдайында терминология әр түрлі болуы мүмкін. Техникалық тұрғыдан алғанда, финализер деструкторларды білдіру үшін де қолданылуы мүмкін, өйткені олар да аяқталуды орындайды, сондай-ақ кейбір ерекше айырмашылықтар бар – терминологияны қараңыз. Сондай-ақ, "final" термині мұрагерлікке жол бермейтін класты да білдіреді; бұл екеуі байланыссыз.

Терминология

Финализатор мен финализация, деструктор мен деструкция терминологиясы авторлар арасында әртүрлі және кейде түсініксіз болады. Көпте қолданылатынында, деструктор – объекті жойылғанда детерминистік түрде шақырылатын әдіс, ал оның үлгісі – C++ деструкторлары; ал финализатор қоқыс жинаушымен детерминистік емес түрде шақырылады, ал оның үлгісі – Java finalize әдістері. Сілтеме санау арқылы қоқыс жинауды іске асыратын тілдер үшін терминология әртүрлі, мысалы, Objective C және Perl тілдері деструкторды қолданса, Python сияқты тілдер финализаторын қолданады (спецификация бойынша Python қоқыс жиналады, бірақ 2.0 нұсқасынан бастап CPython-ның анықтамалық іске асырылуы сілтеме санау мен қоқыс жинаудың комбинациясын қолданады). Бұл сілтеме санаудың объектінің өмір сүру мерзімін жартылай детерминистік ететінін көрсетеді: циклға қатыспаған объектілер сілтеме санауы нөлге жеткенде детерминистік түрде жойылады, ал циклға қатысқан объектілер қоқыс жинаудың жеке түрінің бөлігі ретінде детерминистік емес түрде жойылады. Кейбір тар техникалық қолданыста конструктор мен деструктор – бұл тіл деңгейінің терминдері, яғни класс ішінде анықталған әдістер, ал инициализатор мен финализатор – бұл іске асыру деңгейінің терминдері, яғни объекті құрылғанда немесе жойылғанда шақырылатын әдістер. Мысалы, C# тілінің бастапқы спецификациясы қоқыс жиналатын болса да, "деструкторларға" сілтеме жасады, ал Common Language Infrastructure (CLI) және оның орындалу ортасы Common Language Runtime (CLR) ретінде іске асырылуы "финализаторларға" сілтеме жасады. Бұл C# тіл комитетінің жазбаларында көрініс тапқан, онда былай делінген: "C# компиляторы деструкторларды [көбінесе] инстанциялық финализаторларға компиляциялайды". Бұл терминология шатастыратындықтан, C# спецификациясының соңғы нұсқалары тіл деңгейінің әдісін "финализаторлар" деп атайды.

Қолдану

Финалдастыру көбінесе тазалау үшін, жадты немесе басқа ресурстарды босату үшін қолданылады: қолмен жад басқару арқылы бөлінген жадты деаллокациялау; егер сілтемелерді санау қолданылса, сілтемелерді тазалау (сілтеме санауларын кеміту); ресурстарды босату, әсіресе ресурстарды алу инициализациясы (RAII) идиомасында; немесе объектіні тіркеуден шығару. Тілдер арасында финалдастыру мөлшері айтарлықтай өзгереді, C++-тағы кең финалдастырудан – онда қолмен жад басқару, сілтеме санау және детерминистік объектілердің өмір сүру ұзақтығы бар; Java-дағы финалдастырудың көбінесе жоқтығынан – онда детерминистік емес объектілердің өмір сүру ұзақтығы бар және көбінесе іздеу қоқыс жинағышымен жүзеге асырылады. Компилятор, интерпретатор немесе орындау уақыты жүзеге асыратын нақты (пайдаланушымен көрсетілген) финалдастыру аз немесе мүлдем болмауы да мүмкін, бірақ маңызды жасырын финалдастыру болады; бұл Python-ның CPython анықтамалық нұсқасындағы автоматты сілтеме санау жағдайында немесе Apple-дің Objective-C нұсқасындағы автоматты сілтеме санауда жиі кездеседі, олар екеуі де финалдастыру кезінде сілтемелерді автоматты түрде бұзады. Финализатор кез келген кодты қамтуы мүмкін; ерекше күрделі қолданылуы – объектіні автоматты түрде объекттер жинағына қайтару. Финалдастыру кезінде жадты деаллокациялау C++ сияқты тілдерде жиі кездеседі, онда қолмен жад басқару стандартты, бірақ басқарылатын үйіндіден тыс бөлінген жадты басқару тілінде де (тілге сыртқы) болады; Java-да бұл Java Native Interface (JNI) және New I/O (NIO) нысандарымен кездеседі. Соңғысы қоқыс жинағыштың осы сыртқы ресурстарды қадағалай алмауына байланысты проблемалар тудыруы мүмкін, сондықтан олар жеткілікті түрде жиналмайды және басқарылмаған жадты сарқып кетуден жад қателеріне әкелуі мүмкін – мұны жергілікті жадты ресурс ретінде қарастыру және төменде талқыланғандай, жою үлгісін пайдалану арқылы болдырмауға болады. Финализаторлар, әдетте, деструкторларға қарағанда әлдеқайда аз қажет және әлдеқайда аз қолданылады. Олар әлдеқайда аз қажет, өйткені қоқыс жинау жад басқаруды автоматтандырады, және әлдеқайда аз қолданылады, өйткені олар әдетте детерминистік түрде орындалмайды – оларды уақтылы шақыру мүмкін емес, тіпті мүлдем, және орындау ортасын болжауға болмайды – сондықтан детерминистік түрде жасалуы керек кез келген тазалауды басқа әдіспен, көбінесе қолмен жою үлгісі арқылы жасау керек. Java мен Python екеуі де финализаторлар шақырылатындығына кепілдік бермейді, сондықтан оларды тазарту үшін оларға сене алмаймыз. Бағдарламашының олардың орындалуына бақылаудың болмауынан, әдетте, кез келген, бірақ ең қарапайым операцияларды жасау үшін финализаторлардан аулақ болу ұсынылады. Әсіресе, деструкторларда жиі орындалатын операциялар, әдетте, финализаторларға сәйкес келмейді. Жалпы анти-үлгі – финализаторларды деструкторлар ретінде жазу, бұл қажетсіз және тиімсіз, өйткені финализаторлар мен деструкторлардың айырмашылығы бар. Бұл әсіресе C++ бағдарламалаушылары арасында жиі кездеседі, өйткені деструкторлар C++-та ресурстарды алу инициализациясы (RAII) идиомасын ұстанғанда кеңінен қолданылады.

Синтаксисі

Финализерлерді қолданатын бағдарламалау тілдеріне C++/CLI, C#, Clean, Go, Java, JavaScript және Python кіреді. Синтаксис тілге қарай айтарлықтай өзгереді. Java-да финализер – это finalize деп аталатын әдіс, ол Object класының finalize әдісін қайта жазады. JavaScript-те FinalizationRegistry нысан қоқысқа жиналған кезде кері шақыруды сұрауға мүмкіндік береді. Python-да финализер – del деп аталатын әдіс. Perl-де финализер – DESTROY деп аталатын әдіс. C#-да финализер (стандарттың бұрынғы нұсқаларында «деструктор» деп аталған) – бұл ~Foo сияқты, ~ префиксімен кластың атауы болатын әдіс. Бұл C++ деструкторымен бірдей синтаксис, және бұл әдістер бастапқыда C++-қа ұқсас болғандықтан «деструкторлар» деп аталды, бірақ олардың мінез-құлқы әртүрлі болғандықтан туындаған шатасуды болдырмау үшін «финализерлер» деп қайта аталды. C++/CLI-де деструкторлар мен финализерлер бар. Деструктор – ~ префиксімен, C#-дағыдай, кластың атауымен аталатын әдіс, мысалы, ~Foo, ал финализер – ! префиксімен, мысалы, !Foo. Go-да финализерлер стандартты кітапханадағы runtime.SetFinalizer функциясын шақыру арқылы бір ғана сілтемеге қолданылады.

Іске асыру

Финализатор объект қоқыс жиналған кезде шақырылады – объект қоқысқа айналғаннан (жеткілікті емес) кейін, бірақ оның жады босатылмастан бұрын. Финализация детерминистік емес, қоқыс жинаушының қалауы бойынша жүзеге асырылады және мүлде орындалмауы мүмкін. Бұл деструкторлармен қарама-қайшы, олар объект енді қолданылмаған кезде детерминистік түрде шақырылады және бақылаусыз бағдарлама тоқтатылған жағдайда ғана шақырылмайды. Финализаторлар көбінесе объектке қатысты операцияларды орындау қажеттілігіне байланысты инстанция әдістері болып табылады. Қоқыс жинаушы объектінің қайта тірілу мүмкіндігін де ескеруі керек. Көбінесе бұл алдымен финализаторларды орындау арқылы, содан кейін қандай да бір объектілердің қайта тірілгенін тексеру арқылы, егер солай болса, оларды жоюды тоқтату арқылы жасалады. Бұл қосымша тексеру қымбатқа түсуі мүмкін – қарапайым іске асыру тіпті бір ғана объектіде финализатор болса, барлық қоқысты қайта тексереді – және осылайша қоқыс жинауды баяулатып, күрделендіреді. Осы себепті финализаторы бар объектілер финализаторы жоқ объектілерге қарағанда сирек жиналады (тек белгілі бір циклдарда), бұл ресурстардың ағып кетуі сияқты финализацияның жылдамдығына сүйенуден туындаған проблемаларды күшейтеді. Егер объект қайта тірілсе, оның финализаторы келесі жойылған кезде қайта шақырыла ма деген сұрақ туындайды – деструкторлардан айырмашылығы, финализаторлар бірнеше рет шақырылуы мүмкін. Егер қайта тірілген объектілер үшін финализаторлар шақырылса, объектілер қайта-қайта тіріліп, жойылмай қалуы мүмкін; бұл Python 3.4 нұсқасынан бұрынғы Python CPython іске асырылуында және C# сияқты CLR тілдерінде кездеседі. Бұған жол бермеу үшін көптеген тілдерде, соның ішінде Java, Objective C (кем дегенде Apple-дің соңғы нұсқаларында) және Python 3.4 нұсқасынан бастап Python, объектілер ең көп дегенде бір рет финализацияланады, бұл объект әлі финализацияланбаған болса, оны қадағалауды талап етеді. Басқа жағдайларда, атап айтқанда C# сияқты CLR тілдерінде, финализация объектілердің өзінен бөлек қадағаланады және объектілер финализация үшін бірнеше рет тіркелуі немесе тіркелуден шығарылуы мүмкін.

Қиындықтар

Қолданылу тәсіліне байланысты, түпкілікті өңдеулер көптеген мәселелер тудыруы мүмкін, сондықтан көптеген мамандар оларды қолданудан қатаң бас тартады. Бұл мәселелерге мыналар жатады:
Түпкілікті өңдеулер синхрондау механизмдері, мысалы, құлыптар қолданылған жағдайда, белгілі бір ретпен орындалмауы және параллель түрде орындалу мүмкіндігіне байланысты, тұйыққа түсіруі мүмкін. Бағдарлама аяқталу кезінде іске қосылатын түпкілікті өңдеулер әдеттегі ортаға сүйене алмайды, сондықтан дұрыс емес болжамдарға байланысты сәтсіздікке ұшырауы мүмкін. Осы себепті түпкілікті өңдеулер көбінесе аяқталу кезінде іске қосылмайды. Сонымен қатар, объектілер бағдарламалық қателер немесе күтпеген жағдайларға байланысты, қоқысқа жиналуға тиіс уақыттан кейін де қол жетімді болып қалуына байланысты түпкілікті өңдеулер іске қосылмауы мүмкін. Мысалы, Python қателіктерді ұстағанда (немесе интерактивті режимде қателік ұсталмағанда), ол қателік туындаған стек фрейміне сілтеме сақтайды, бұл сол стек фреймінен сілтеме жасалған объектілерді сақтап қалады. Java-да жоғары кластағы түпкілікті өңдеулер төменгі кластағы қоқыс жинау процесін баяулатуы мүмкін, себебі түпкілікті өңдеу төменгі кластағы өрістерге сілтеме жасай алады, сондықтан бұл өріс түпкілікті өңдеу орындалғаннан кейін келесі циклға дейін қоқысқа жиналмайды. 1995 жылы Java-ның пайда болуымен бірге, терминді танымал еткен және оны қоқыс жинаумен байланыстырған finalize әдістері енгізілді, ал осы кезден бастап көптеген бағдарламалау тілдері осы айырмашылықты ескереді және әсіресе қоқыс жинау контекстінде "түпкілеу" терминін қолданады.