Az intelligens mutató egy memória indirekt idióma, amelyet széles körben használnak magas szintű nyelveken, például C++ , Rust és így tovább. Általában speciális osztályként valósítják meg (általában paraméterezett ), amely egy normál mutató interfészét utánozza , és hozzáadja a szükséges új funkciókat (például hozzáférési határellenőrzés vagy memóriatisztítás ) [1] .
Az intelligens mutatók használatának fő célja általában a dinamikus memóriakezelés olyan módon történő beágyazása , hogy az intelligens mutatók tulajdonságai és viselkedése utánozzák a normál mutatók tulajdonságait és viselkedését . Ugyanakkor felelősek az allokált erőforrások időbeni és pontos felszabadításáért, ami leegyszerűsíti a kódfejlesztési és hibakeresési folyamatot, kiküszöböli a memóriaszivárgást és a lógó linkek előfordulását [2] .
Ezeket általában olyan objektumoknál használják, amelyek speciális műveletekkel rendelkeznek: „Referenciaszám növelése” ( COMAddRef() -ban ) és „Referenciaszám csökkentése” ( COM-ban). Leggyakrabban az ilyen objektumok egy speciális osztályból vagy interfészből öröklődnek (például a COM-ban). Release()IUnknown
Ha új hivatkozás jelenik meg egy objektumra, akkor a „hivatkozások számának növelése” műveletet hívják meg, ha pedig megsemmisül, a „hivatkozások számának csökkentése” műveletet hívják meg. Ha a "hivatkozások csökkentése" művelet eredményeként egy objektumra mutató hivatkozások száma nulla lesz, akkor az objektum törlődik.
Ezt a technikát automatikus referenciaszámlálásnak nevezik . Összeegyezteti az objektum címét tároló mutatók számát az objektumban tárolt hivatkozások számával, és ha ez a szám eléri a nullát, az objektum törlését okozza. Előnye a viszonylag nagy megbízhatóság, a gyorsaság és a könnyű implementáció C++ nyelven . Hátránya, hogy körkörös hivatkozások esetén nehezebben használható ("gyenge hivatkozások" használatának szükségessége).
Az ilyen mutatóknak két típusa van: az objektumon belüli számlálótárolóval és kívüli pulttárolóval.
A legegyszerűbb lehetőség a számláló tárolása egy kezelt objektumon belül. A COM -ban a referencia-számlálású objektumok a következőképpen valósulnak meg:
Ugyanúgy végrehajtva boost::intrusive_ptr.
A std::shared_ptrreferenciaszámlálók az objektumon kívül, speciális adatstruktúrában tárolódnak. Egy ilyen intelligens mutató kétszer akkora, mint egy szabványos (két mezője van, az egyik a számlálószerkezetre, a másik a kezelt objektumra mutat). Ez a kialakítás lehetővé teszi:
Mivel a számlálószerkezet kicsi, kiosztható például az objektumkészleten keresztül .
Tegyük fel, hogy két objektum van, és mindegyiknek van egy tulajdonmutatója. Az első objektum mutatójához a második objektum címe van hozzárendelve, a másodikban lévő mutató pedig az első objektum címe. Ha most minden külső (vagyis nem ezekben az objektumokban tárolt) mutatóban két adott objektum új értéket kap, akkor az objektumok belsejében lévő mutatók továbbra is egymás tulajdonosai maradnak, és a memóriában maradnak. Ennek eredményeként olyan helyzet áll elő, amikor az objektumok nem érhetők el, azaz memóriaszivárgás .
A körkörös hivatkozások problémáját vagy az adatszerkezetek megfelelő kialakításával, vagy a szemétgyűjtéssel , vagy kétféle hivatkozás használatával oldjuk meg: erős (tulajdonos) és gyenge (például nem birtokló std::weak_ptr).
A megosztott tulajdonjog mutatói gyakran túl nagyok és „nehézek” a programozó feladataihoz: például létre kell hoznia egy N típusú objektumot, birtokolnia kell, időnként hozzá kell férnie a virtuális funkcióihoz, majd megfelelően törölnie kell. Ehhez használja a "kistestvért" - az egyedüli tulajdon mutatóját.
Az ilyen mutatók új érték hozzárendelésekor vagy önmaguk törlésekor törlik az objektumot. Egyéni vállalkozási mutatók hozzárendelése csak az egyik mutató megsemmisítésével lehetséges - így soha nem lesz olyan, hogy két mutató ugyanazt az objektumot birtokolja.
Hátrányuk az, hogy egy objektumot nehéz kihagyni a mutató hatóköréből.
A legtöbb esetben, ha van egy függvény, amely egy tömböt kezel, két dolog egyikét írják le:
void sort ( size_t size , int * data ); // mutató + méret void rendezés ( std :: vektor < int >& adatok ); // specifikus memóriastruktúraAz első kizárja az automatikus tartományellenőrzést. A második korlátozza a std::vector's alkalmazhatóságát, és nem lehet rendezni például egy tömb karakterláncát vagy egy másik tömb egy részét vector.
Ezért a mások memóriapuffereit használó függvények kifejlesztett könyvtáraiban „könnyű” adattípusokat használnak, mint pl.
sablon < classT > _ struct Buf1d { T * adat ; size_t size ; Buf1d ( std :: vektor < T >& vec ); T & operátor []( size_t i ); };Gyakran használják karakterláncokhoz: az elemzéshez , a szövegszerkesztő futtatásához és más speciális feladatokhoz saját adatstruktúrákra van szükség, amelyek gyorsabbak, mint a szokásos karakterlánc-manipulációs módszerek.