C++ itsessään ei sisällä tukia ns. smart pointtereille. Nämä älykkäät osoittimet ovat hyvin käteviä, sillä ne mahdollistavat dynaamisesti luotujen olioiden turvallisen ja tehokkaan käsittelyn esimerkiksi funktion ja kutsujan välillä.
Idea tekemässäni shared pointer:ssa on seuraava: olio luodaan dynaamisesti osoitininstanssin sisään. Samalla luodaan myös luku, joka kertoo, kuinka monessa instanssissa dynaamisesti luotu olio on. Kun osoitininstanssi tuhotuu (esim. scopen loppuessa) tätä arvoa vähennetään yhdellä. Kun osoittimesta kopioidaan toinen osoitin, arvoa lisätään yhdellä. Jos arvo pääsee menemään nollaan, tuhotaan dynaamisesti luotu olio.
Erityisen hyödyllisiä jaetut osoittimet ovat funktioden paluuarvoissa, joiden koko on niin suuri, että niiden kopioimen kutsujalle ei olisi järjevää.
SharedPtr.h
#ifndef __Shared_ptr_h__ #define __Shared_ptr_h__ #include <cassert> /******************* (c) Matti Lankinen ******************** * Tämä tiedosto dynaamisesti luotujen olioiden säilytysluokan, * ns. smart pointterin. Luokan sisälle varastoitu olio tuhotaan * vasta sitten, kun kaikki olion kopiot ja itse olio on tuhoutunut. * Tämä luokka on tehokas tapa hallita dynaamista muistia. * * Koodi on vapaasti käytettävissä ja muunneltavissa. */ /* Jos haluat, että SharedPtr voidaan kopioida mistä tahansa toisen tyyppisestä * SharedPtr:stä, aseta tämän symbolin arvo 1:ksi. Tämä tyyppikonversio on tarkoitettu * vain sukulais-pointtereita varten, ja kahden toisilleen tuntemattoman luokan * pointterin käyttö on hyvin vaarallista. Tässä tapauksessa onneksi pysäytetään * ohjelman suoritus assertioon. * * HUOM! TARKKANA TÄMÄN SYMBOLIN KANSSA! */ #define __Enable_Shared_Ptr_Type_Conversion__ 0 template <typename T> class SharedPtr { public: /** Luo uuden osoittimen, alustaa sen nollalla. */ SharedPtr() : _pRef(0), _pCount(0) {} /** Luo uuden osoittimen ja kopioi sen tiedot toisen osoittimen tiedoilla. */ SharedPtr(const SharedPtr& _ptr) : _pRef(0), _pCount(0) { if( _ptr._pRef ) { _pRef = _ptr._pRef; _pCount = _ptr._pCount; (*_pCount)++; } } /** Luo uuden osoittimen ja lisää siihen aivan uuden kapselin. */ explicit SharedPtr(T* _ref) { assert( _ref ); _pRef = _ref; _pCount = new size_t(1); } /** Luo uuden osoittimien annetuista tiedoista. Vain kokeneemmille käyttäjille. */ explicit SharedPtr(T* _ref, size_t* _useCount) { assert( _ref ); _pRef = _ref; _pCount = _useCount; (*_pCount)++; } #if __Enable_Shared_Ptr_Type_Conversion__ == 1 /** Luo uuden osoittimen kopioiden tiedot toisentyyppisestä osoittimesta. @remarks Jos tietojen kopioiminen ei onnistu (tiedot eivät ole sukua keskenään), heitetään assertio. */ template<class B> SharedPtr(const SharedPtr<B>& _ptr) { T* _ref = (T*)_ptr._pRef; assert( _ref ); _pRef = _ref; _pCount = _ptr._pCount; (*_pCount)++; } #endif // __Enable_Shared_Ptr_Type_Conversion__ == 1 /** Tuhoaa osoittimen ja sen sisältämän datan, jos osoitin on viimeinen sisältämälleen viitteelle. */ ~SharedPtr() { clear(); } /** Puhdistaa nykyisen kapselin datan pois instanssista ja hoitelee tarvittaessa kapselin tuhouksen. */ void clear() { if( _pRef ) { if( !(--(*_pCount)) ) { delete _pRef; _pRef = 0; delete _pCount; _pCount = 0; } } } /** Palauttaa tiedon siitä, onko instanssissa kapselia. */ bool isNull() const { return !_pRef; } /** Sijoittaa toisen osoittimen tiedot instanssiin. @remarks Ennen sijoitusta instanssin tiedot puhdistetaan, ja jos instanssi on viimeinen, jossa kapseli on, tuhotaan kapseli ja sijoitetaan uuden osoittimen kapseli instanssiin. Uuden osoittimen kapseli voi olla myös NULL, tämä operaattori osaa ottaa sen huomioon. */ SharedPtr& operator =(const SharedPtr& _ptr) { clear(); _pRef = _ptr._pRef; _pCount = _ptr._pCount; if( _pRef ) (*_pCount)++; return *this; } #if __Enable_Shared_Ptr_Type_Conversion__ == 1 /** Sijoittaa toisen, eri tyyppisen, osoittimen tiedot instanssiin. @remarks Ennen sijoitusta instanssin tiedot puhdistetaan, ja jos instanssi on viimeinen, jossa kapseli on, tuhotaan kapseli ja sijoitetaan uuden osoittimen kapseli instanssiin. Uuden osoittimen kapseli EI VOI OLLA NULL, muuten käynnistetään assertio. */ template<class B> SharedPtr& operator =(const SharedPtr<B>& _ptr) { clear(); assert( _ptr._pRef ) T* _ref = (T*)_ptr._pRef; assert( _ref ); _pRef = _ref; _pCount = _ptr._pCount; (*_pCount)++; } #endif // __Enable_Shared_Ptr_Type_Conversion__ == 1 /** Sijoittaa kapselin tiedot osoittimeen. @remarks Ennen sijoitusta instanssin tiedot puhdistetaan, ja jos instanssi on viimeinen, jossa kapseli on, tuhotaan kapseli ja sijoitetaan uusi kapseli osoittimeen. */ SharedPtr& operator <<(T* _ref) { clear(); assert( _ref ); _pRef = _ref; _pCount = new size_t(1); return *this; } /** Palautta true, jos osoittimessa on tietoa sisältävä kapseli. Muussa tapauksessa palautetaan false. */ operator bool() const { return _pRef != 0; } /** Vertailee kahta osoitinta keskenään. @remarks Jos osoittimet ovat NULL-osoittimia, eivät ne ole kuitenkaan samoja. Jos toinen ei ole NULL-osoitin, verrataan osoittimien sisältöä ja palautetaan vertailutulos sen mukaan. */ bool operator ==(const SharedPtr& _ptr) const { if( !_pRef ) return false; return _pRef == _ptr._pRef; } /** Vertailee kahta osoitinta keskenään. @remarks Jos osoittimet ovat NULL-osoittimia, eivät ne ole kuitenkaan samoja. Jos toinen ei ole NULL-osoitin, verrataan osoittimien sisältöä ja palautetaan vertailutulos sen mukaan. */ bool operator !=(const SharedPtr& _ptr) const { if( !_pRef ) return true; return _pRef != _ptr._pRef; } /** Palauttaa kapselin osoitteen. */ T* get() { return _pRef; } /** Palauttaa kapselin vakio-osoitteen. */ const T* get() const { return _pRef; } /** Palauttaa viittauksen kapseliin. */ T& operator *() { assert(_pRef); return *_pRef; } /** Palauttaa vakioviittauksen kapseliin. */ const T& operator *() const { assert(_pRef); return *_pRef; } /** Palauttaa kapselin osoitteen. Tarkoitettu suorakäsittelyä varten. */ T* operator ->() { assert(_pRef); return _pRef; } /** Palauttaa kapselin vakio-osoitteen. Tarkoitettu suorakäsittelyä varten. */ const T* operator ->() const { assert(_pRef); return _pRef; } /** Palauttaa käyttäjälle osoittimen kapselin käytön, eli kuinka monessa osoittimessa kapseli on. @remarks Jos kapseli on NULL-palautetaan 0. */ size_t getUseCount() const { if(!_pCount) return 0; return *_pCount; } /** Palauttaa osoittimen lukumääriin. Tämä on tarkoitettu vain kokeneemmille käyttäjille. */ size_t* getCountPtr() { return _pCount; } friend class SharedPtr; private: T* _pRef; size_t* _pCount; }; #endif
Esimerkki
#include <string> #include <iostream> #include <vector> #include "SharedPtr.h" typedef std::vector<std::string> StringVector; typedef SharedPtr<StringVector> StringVectorPtr; // tämä funktio rasittaisi todella paljon ohjelmaa, jos palautettaisiin StringVector // ensiksi pitäisi luoda vektori ja lisätä siihen merkkijonot // tämän jälkeen pitäisi arvot vielä kopioida uudestaan funktion kutsujalle - ei hyvä :( // jätetään siis toinen vaihe väliin käyttämällä shared pointer:eita :) StringVectorPtr createStrings() { StringVectorPtr ptr( new StringVector() ); for( int i = 0 ; i < 200 ; i++ ) ptr->push_back( "fooofooofooofooofooofooofooofooofooofooofooofooofooofooo" ); return ptr; } int main() { StringVectorPtr strings = createStrings(); for( StringVector::iterator i = strings->begin() ; i != strings->end() ; i++ ) std::cout << *i << std::endl; return 0; }
Ihan kiva koodiharjoitus. Näissä viitelaskuripointtereissa pitää kyllä muistaa varoa syklejä. Tuntuu oudolta, että luokan SharedPtr
esittelyn sisällä pitää sanoa, että friend class SharedPtr
.
Pidän kyllä C++:n standardikirjastosta nykyisin löytyvää std::shared_ptr
:ää vähän luotettavampana ja siinä on otettu pari muutakin juttua aika tarkasti huomioon. Nämä omatekemät kopiot eivät aina ole ihan kirjastolaatua. Nuorena miehenä isommat pojat kertoivat, että erilaisia smart pointereita oli tulossa standardiin, mutta lopulta vain auto_ptr
hyväksyttiin. Nyt shared_ptr
ja sen pikkuveli weak_ptr
ovat standardin lisäyksessä.
Esimerkkitapaus on siinä vähän huono, että standardikirjaston containereissa on valmiina jutut, joilla kallista kopiointia pystyy välttämään ilman mitään smart pointereita. Kaikki vähänkään järkevät ajanmukaiset kääntäjät myös optimoivat pois turhat paluuarvon kopiot.
#include <string> #include <iostream> #include <vector> typedef std::vector<std::string> StringVector; StringVector createStrings() { // Huom: vain yksi mahdollinen paluuarvomuuttuja, // niin optimointi voi välttää turhat kopioinnit StringVector v; // Huom: miten olisi v.reserve(200) for (int i = 0 ; i < 200 ; i++) v.push_back("fooofooofooofooofooofooofooofooofooofooofooofooofooofooo"); return v; } void test1() { // Huom: alustus; ei kopiointeja, kun optimoitu käännös StringVector strings = createStrings(); for (StringVector::iterator i = strings.begin() ; i != strings.end() ; ++i) std::cout << *i << std::endl; } void test2() { StringVector strings; // Huom: sisältöjen vaihto, ei kopiointeja createStrings().swap(strings); for (StringVector::iterator i = strings.begin() ; i != strings.end() ; ++i) std::cout << *i << std::endl; }
Muutoin shared pointer kyllä voi auttaa suurten olioiden kanssa. Muutoin jaettu olion omistaminen ei aina kyllä ole niin loistava juttu. Yleensä on paljon yksinkertaisempaa ja tehokkaampaakin, jos oliolla on tavalla tai toisella vain yksi omistaja ja muut vain viittailevat olioon.
Mutta kyllähän tästä esimerkistä tuo idea tulee hyvin esiin.
Esimerkki on kyllä hieman huono, mutta shared pointerin idea tulee varmasti tutuksi. Sanoit, että standardikirjastosta löytyy shared_ptr? Itse en löydä tätä millään? Onko se missä headerissä? Itsekin käyttäisin std-kamaa, jos löytäisin. :)
Standardin lisäyksellä Koo tarkoittanee Technical Report 1:tä.
http://en.wikipedia.org/wiki/Technical_Report_1
http://fi.wikipedia.org/wiki/Technical_Report_1
Shared_ptr ja weak_ptr löytyvät siis otsikon <memory> kautta, jos löytyvät. Suomenkielisellä Wikipedian sivulla oli maininta, että ne ovat nimiavaruudessa std::tr1 tarpeeksi uusilla g++-kääntäjillä.
Pointtereista voisi mainita myös scoped_ptr:in. Se on Boostissa.
Aihe on jo aika vanha, joten et voi enää vastata siihen.