Խելացի ցուցիչ

Համակարգչային գիտությունում, խելացի ցուցիչը տվյալների աբստրակտ տիպ է, որը կատարելով ցուցչի դեր, միաժամանակ տրամադրում է լրացուցիչ հնարավորություններ, ինչպիսին են՝ հիշողության կառավարում կամ սահմանների ստուգում։ Այս լրացուցիչ հնարավորությունները ստեղծված են ցուցչի սխալ օգտագործման հետևանքով առաջացած սխալների քանակը նվազեցնելու նպատակով՝ պահպանելով վերջինի արդյունավետությունը։ Խելացի ցուցիչները, սովորաբար, պահպանում են հիշողության ճանապարհը, որի վրա ցույց են տալիս։ Նրանք կարող են օգտագործվել նաև այլ ռեսուրսների կառավարման համար, օրինակ՝ ցանցային կապի։

Ցուցիչների սխալ օգտագործումը հանդիսանում է ծրագրային սխալների հիմնական աղբյուրներից մեկը։ Խելացի ցուցիչները կանխում են հիշողության արտահոսքի դեպքերի մեծ մասը, հիշողության ազատումը դարձնելով ավտոմատացված։ Խելացի ցուցիչների կողմից կառավարվող ռեսուրսները ինքնուրույն կերպով ազատվում են, երբ վերանում է նրա վերջին (կամ միակ) տերը, օրինակ այն պատճառով, որ այն անցել է թույլատրելի սահմանները։ Խելացի ցուցիչները կանխում են նաև կախված ցուցիչների իրավիճակները, քանի որ ռեսուրսների ազատումը տեղի է ունենում ամենավերջում (երբ ռեսուրսը չի օգտագործվում ոչ–ոքի կողմից)։

Գոյություն ունի խելացի ցուցիչների մի քանի տիպ։ Որոշները աշխատում են հղումներ հաշվելու մեթոդով, մյուսները՝ վերագրելով օբյեկտի սեփականատիրությունը միակ ցուցչի։ Այն դեպքում, երբ ծրագրավորման լեզուն ունի ավտոմատացված աղբի հավաքում (օրինակ Java կամ C#), ապա հիշողության կառավարման համար խելացի ցուցիչները այլևս անհրաժեշտ չեն, չնայած կարող են օգտագործվել այլ ռեսուրսների կառավարման համար։

Խելացի ցուցիչները C++ լեզվում 
C++ լեզվում, խելացի ցուցիչները կարող են իրականացվել որպես կաղապարային դաս՝ ավանդական ցուցչի դերը կատարելու հետ մեկտեղ տրամադրելով հիշողության կառավարման լրացուցիչ ալգորիթմներ։

Խելացի ցուցիչները կարող են հեշտացնել ծրագրավորումը՝ արտահայտելով ցուցչի օգտագործման եղանակները հենց տոպով։
Օրինակ, երբ C++ ֆունկցիան վերադարձնում է ցուցիչ, հնարավոր չէ պարզել, թե տեղեկատվությունը օգտագործելուց հետո արդյո՞ք կանչո՛ղը պետք է ազատի հիշողությունը։
some_type* ambiguous_function(); // Ի՞նչ պետք է արվի արդյունքի հետ
Ավանդաբար, այս խնդիրը լուծվում էր մենկաբանությունների միջոցով, սակայն այդ ճանապարհը կարող է հանգեցնել սխալների։ Տվյալ մեթոդի հետևանքով առաջացող խնդիրները կարելի է շրջանցել վերադարձնելով unique_ptr,
unique_ptr<some_type> obvious_function1();
Այս դեպքում ֆունկցիան բացահայտորեն հաստատում է, որ կանչողը պետք է ստանձնի արդյունքի սեփականատեր հանդիսանալու դերը։
Մինչև C++11ը, unique_ptr–ն կարող է փոխարինվել auto ptr–ով։

unique_ptr 

C++11–ը տրամադրում է std::unique_ptr, որը նկարագրված է <memory> գլխային ֆայլում։

std::auto_ptr–ի պատճենող կառուցիչները և վերագրման օպերատորները իրականում չեն պատճենում տեղադրված ցուցիչը։ Փոխարենը, նրանք տեղափոխում են ցուցիչը՝ նախորդ std::auto_ptr օբյեկտը թողնելով դատարկ։ Սա եղանակներից մեկն է, որի միջոցով կարելի է երաշխավորել, որ միայն մեկ std::auto_ptr օբյեկտ կարող է սեփականաշնորհել ցուցիչը ժամանակի տվյալ պահին։ Սա նշանակում է, որ չպետք է օգտագործել std::auto_ptr, երբ պատճենահանման քերականության օգտագործումը անխուսափելի է։

C++11 ստանդարտը տրամադրում է տեղափոխման քերականություն, որը թույլ է տալիս արժեքների տեղափոխությունը բացահայտորեն ներկայացնել այլ գործողություն, քան նրանց պատճենումը։ Ստանդարտը թույլ է տալիս նաև օբյեկտների պատճենման բացահայտ կանխում։ Քանի որ std::auto_ptr–ն արդեն գոյություն ուներ իր սեփական պատճենման քերականությամբ հանդերձ, այն չէր կարող առանց անհամատեղելիության փոխարինվել միայն տեղախոխման քերականությամբ ցուցչով։ Այդ պատճառով C++11–ը ներմուծում է ցուցչի նոր տիպ՝ std::unique_ptr։

Ցուցչի այս տիպում պատճենման կառուցիչները և վերագրման օպերատորները բացահայտորեն ջնջված են, ինչը նշանակում է, որ ցուցիչը չի կարող պատճենվել։ Այն նաև չի կարող տեղափոխվել օգտագործելով std::move–ն, որը թույլ է տալիս փոխանցել սեփականատիրությունը մի std::unique_ptr օբյեկտից մյուսին։

std::unique_ptr<int> p1(new int(5));
std::unique_ptr<int> p2 = p1; // թարգմանության սխալ
std::unique_ptr<int> p3 = std::move(p1); // փոխանցում է սեփականատիրությունը. այժմ p3–ը հանդիսանում է հիշողության սեփականատեր, իսկ p1–ը անաշխատունակ է։

p3.reset(); //ազատում է հիշողությունը
p1.reset(); //ոչինչ չի անում

std::auto_ptr–ն դեռևս հասանելի է, սակայն C++11–ում համարվում է հնացած։

shared_ptr և weak_ptr 
C++11–ը պարունակում է shared_ptr և weak_ptr, որոնք հիմնված են Boost գրադարանների հիման վրա։ C++ Տեխնիկական Զեկույց 1ում վերջիններս առաջին անգամ ներմուծվեցին ստանդարտի մեջ, բայց C++11–ում նրանք ձեռք են բերում լրացուցիչ հնարավորությունները՝ հավասարվելով Boost–ի տարբերակներին։

shared_ptr–ն իրենից ներկայացնում է սեփականատիրության իրավունք ցուցչի համար. Ամեն մի shared_ptr օբյեկտ սեփականաշնորհում է նույն ցուցչիը. Այդ ցուցչիը ազատվում է միայն այն դեպքում, երբ ծրագրում վերանում են բոլոր այն shared_ptr օբյեկտները, որոնց օգտագործում են այդ ցուցչիը։

std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; //այժմ երկուսն էլ սեփականատեր են

p1.reset(); //հիշողությունը դեռ ազատված չէ (p2–ի պատճառով)
p2.reset(); //հիշողությունը ազատվում է, քանի որ չկա ցուցիչը օգտագործող ոչ–մի օբյեկտ

shared_ptr–ն օգտագործում է հղումները հաշվելու մեթոդը, այսպիսով ցիկլիկ հղումները հանդիսանում են պոտենցիալ խնդիր։ Խնդիրը շրջանցելու համար օգտագործվում են std::weak_ptr։ Տեղակայված օբյեկտը ազատվում է միայն այն դեպքում, երբ միակ հղումները օբյեկտի վրա հանդիսանում են weak_ptr տիպի հղումներ։ weak_ptr–ն չի երաշխավորում, որ օբյեկտը դեռ գոյությունը ունի, բայց այն կարող է հարցում անել, ռեսուրսի տրամադրման համար։

std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; //p1 հանդիսանում է հիշողության սեփականատեր

{
  std::shared_ptr<int> p2 = wp1.lock(); //այժմ երկուսն էլ սեփականատեր են
  if(p2) //միշտ ստուգեք, արդյո՞ք հիշողությունը դեռ գոյություն ունի
  { 
    //անել ինչ–որ բան p2–ով
  }
} //p2–ը վերացած է. p1 հանդիսանում է հիշողության սեփականատեր.

p1.reset(); //հիշողությունը ազատված է

std::shared_ptr<int> p3 = wp1.lock(); //հիշողություն չկա, հետևաբար կստանանք դատարկ shared_ptr.
if(p3)
{
  //այս կոդը չի կատարվի
}

Զուգահեռ կատարման երաշխիքներ 
Մի քանի հոսքեր կարող են միաժամանակ անխափան կերպով մուտք գործել std::shared_ptr և std::weak_ptr օբյեկտներ, որոնք ցույց են տալիս նույն օբյեկտի վրա։

Հղվող օբյեկտները սակայն, պետք է նույնպես պաշտպանված լինեն զուգահեռ կատարման դեպքում առաջացող խնդիրներից։

Ծանոթագրություններ

Արտաքին հղումներ 
 Sample chapter "Smart Pointers" from the book  Modern C++ Design: Generic Programming and Design Patterns Applied by Andrei Alexandrescu, Addison-Wesley, 2001.
 Code example "countptr.hpp" from the book The C++ Standard Library - A Tutorial and Reference by Nicolai M. Josuttis
 "Boost Smart Pointers"
 Article "The New C++: Smart(er) Pointers" by Herb Sutter August 1, 2002
 "Smart Pointers - What, Why, Which?" by Yonat Sharon
 "Smart Pointers Overview " by John M. Dlugosz
 The YASPER library Yet Another Smart Pointer implementation in C++
 Smart Pointers in Delphi
 Smart Pointers in Rust

Ծրագրավորում