Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: C++: Jaetut osoittimet

Mazzimo [20.04.2007 21:05:44]

#

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;
}

koo [23.04.2007 23:02:19]

#

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.

Mazzimo [25.04.2007 16:14:49]

#

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. :)

Pekka Karjalainen [26.04.2007 17:45:04]

#

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.

http://www.boost.org/libs/smart_ptr/scoped_ptr.htm

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta