Funktio-osoittimet toimivat, kuten niiden nimikin jo kertoo: ne säilövät funktioiden osoitteita. C++:ssa jokaisella funktiolla on oma osoitin ja näitä osoittimia voidaan ottaa talteen ja niitä kutsumalla myös kutsua osoittimen taakse kätkeytyviä funktioita. Tämä on hyvin tehokas tapa tehdä ns. callback, eli isäntä asettaa luodulle instanssille callback-funktion, jota luotu instanssi sitten tarpeen vaatiessa kutsuu.
Funktio-osoittimet ovat C++:ssa hyvin vaikeita itsessään, mutta niiden käsittelyyn on myös mahdollista tehdä luokka. Tämän luokan taakse voidaan kätkeä niin yksittäiset funktiot, kuin luokan instanssien jäsenfunktiot. Lisäksi funktion kutsuminen on tämän luokan avulla helppoa.
Tekemäni luokka sisältää seuraavat ominaisuudet:
* yksittäisen funktio tallettaminen
* instanssin jäsenfunktion tallettaminen
* oma parametri (vain yksi)
* oma paluuarvo (void ei tuettuna)
* () -operaattori tallennetun funktion suorittamiseen
* funktio-osoittimen kopioimisen toiseen
* assertion null-osoittimia varten
* null-osoittimen tarkistus
Ensimmäisessä koodilistauksessa on headeri, joka sisältää itse funktio-osoittiminen toteutuksen. Loput kolme koodia ovat esimerkkejä (tökeröitä sellaisia :P).
FunctionPtr.h
ifndef __function_ptr_h__ #define __function_ptr_h__ /******************* (c) Matti Lankinen ******************** * Tämä tiedosto sisältää luokan funktioiden (yksittäisten * ja jäsenfunktioiden osoittimien tallennukseen ja käyttää * niitä näin callback:eina. * * Koodi on vapaasti käytettävissä ja muunneltavissa. */ #include <cassert> /** FunktioPtr-luokan tarvitsema jäsen joka sisältää virtuaalimetodit funktio-osoittimien käsittelyyn. @remarks Tämä on abstrakti kantaluokka josta peritään luokat _SingleBuilder ja _MemberBuilder. */ template <typename ret, typename args> class _BuilderBase { public: _BuilderBase() {} virtual ~_BuilderBase() {} virtual ret operator() (args a) const = 0; virtual _BuilderBase* copy() const = 0; }; /** FunktioPtr-luokan tarvitsema jäsen joka sisältää virtuaalimetodit funktio-osoittimien käsittelyyn. @remarks Tämä on abstrakti kantaluokka josta peritään luokat _SingleBuilder ja _MemberBuilder. @remarks Erikoistapaus void-tyyppiselle parametrille. */ template <typename ret> class _BuilderBase <ret, void> { public: _BuilderBase() {} virtual ~_BuilderBase() {} virtual ret operator() () const = 0; virtual _BuilderBase* copy() const = 0; }; /** FunktioPtr-luokan tarvitsema kapselointi-instanssi funktion osoittimelle. @remarks Periytetty _BuilderBase-luokasta. */ template <typename ret, typename args> class _SingleBuilder : public _BuilderBase<ret, args> { public: typedef ret (*Pointer)(args); _SingleBuilder(Pointer ptr) : myPtr(ptr) { } virtual ret operator() (args a) const { return myPtr(a); } virtual _BuilderBase<ret,args>* copy() const { return new _SingleBuilder<ret,args>( myPtr ); } protected: Pointer myPtr; }; /** FunktioPtr-luokan tarvitsema kapselointi-instanssi funktion osoittimelle. @remarks Periytetty _BuilderBase-luokasta. @remarks Erikoistapaus void-tyyppiselle parametrille. */ template <typename ret> class _SingleBuilder<ret, void> : public _BuilderBase<ret, void> { public: typedef ret (*Pointer)(void); _SingleBuilder(Pointer ptr) : myPtr(ptr) { } virtual ret operator() () const { return myPtr(); } virtual _BuilderBase<ret,void>* copy() const { return new _SingleBuilder<ret,void>( myPtr ); } protected: Pointer myPtr; }; /** FunktioPtr-luokan tarvitsema kapselointi-instanssi luokan jäsenfunktion osoittimelle. @remarks Periytetty _BuilderBase-luokasta. */ template <typename ret, typename args, typename T> class _MemberBuilder : public _BuilderBase<ret, args> { public: typedef ret (T::* Pointer)(args); _MemberBuilder(Pointer ptr, T* instance) { myInstance = instance; myPtr = ptr; } virtual ret operator() (args a) const { return (myInstance->*myPtr)(a); } virtual _BuilderBase<ret,args>* copy() const { return new _MemberBuilder<ret,args,T>(myPtr, myInstance); } protected: T* myInstance; Pointer myPtr; }; /** FunktioPtr-luokan tarvitsema kapselointi-instanssi luokan jäsenfunktion osoittimelle. @remarks Periytetty _BuilderBase-luokasta. @remarks Erikoistapaus void-tyyppiselle parametrille. */ template <typename ret, typename T> class _MemberBuilder<ret,void,T> : public _BuilderBase<ret, void> { public: typedef ret (T::* Pointer)(void); _MemberBuilder(Pointer ptr, T* instance) { myInstance = instance; myPtr = ptr; } virtual ret operator() () const { return (myInstance->*myPtr)(); } virtual _BuilderBase<ret,void>* copy() const { return new _MemberBuilder<ret,void,T>(myPtr, myInstance); } protected: T* myInstance; Pointer myPtr; }; /** tämä luokka sisältää halutun tyyppisen funktion pointterin. @remarks pointterin voi luoda funktiosta, tai tietyn luokan instanssin jäsenfunktiosta. Luokka on malliluokka, joten siitä vuo luoda minkä tyyppisiä funktio-osoittimia tahansa. Funktiolla on paluu- arvo sekä YKSI (1) parametri, jotka käyttäjä saa itse valita instanssia luodessaan. Jäsenfunktion voi ottaa minkä tahansa luokan instanssista! */ template <typename ret, typename args> class FunctionPtr { public: typedef ret (*Pointer)(args); /** Luo tyhjän instanssin */ FunctionPtr() : ptrCapsule(0) { } /** Luo alustetun instanssin olemassa olevasta osoittimesta. @param ptr Osoitin funktioon. */ FunctionPtr( Pointer ptr ) { ptrCapsule = new _SingleBuilder<ret,args>(ptr); } /** Luo alustetun instanssin halutun luokan olion jäsenfunktioista. @param (ret (T::*f)(args)) osoitin luokan jäsenfunktioon @param instance Osoitin olioista, jonka funktio toteutetaan. */ template <class T> FunctionPtr(ret (T::*f)(args), T* instance) { ptrCapsule = new _MemberBuilder<ret,args,T>(f, instance); } /** Luo uuden instanssin käyttäen käyttäen kopioitavan luokan _BuilderBase-instanssia. @remarks Tämä ei ole tavalliselle käyttäjälle sillä kyseiset instanssit ovat vain FunctionPtr-luokan saavutettavissa. Tätä käytetäänkin vain kopiomuodostimessa FunctionPtr-luokan instansseille, joita käyttäjä voi käyttää. @param capsule Kapseli, joka sisältää toiminnallisuuden. */ FunctionPtr( _BuilderBase<ret,args>* capsule ) { ptrCapsule = capsule; } /** Luo uuden instanssin kopioiden sen annetusta instanssista. @remarks Luodun instanssin ()-operaattori tekee siis täysin samat asiat, mitä kopioitavan instanssin operaattori. @param other Instanssi, josta tiedot kopioidaan. */ FunctionPtr(const FunctionPtr& other) : ptrCapsule(0) { if( other.getPtrCapsule() ) ptrCapsule = other.getPtrCapsule()->copy(); } /** Tuhoaa instanssin ja vapauttaa mahdollisesti osoitinkapselin varaamaan vapaan muistin. */ ~FunctionPtr() { release(); } /** Suorittaa instanssissa olevan funktion. @remarks Sinun täytyy olla varma, että instanssissa on varmasti funktio-osoitin sillä tämä ei tarkasta sitä ja seuraukset huolimattomasta käytöstä voivat olla vakavat. @param a Halutun tyyppinen instanssi, jonka funktio ottaa parametrikseen. @returns Halutun tyyppisen instanssin, jonka funktio palauttaa. */ ret operator() (args a) const { assert( ptrCapsule ); return (*ptrCapsule)(a); } /** Palauttaa käyttäjälle instanssissa olevan kapselin osoitteen. @remarks Palautettava arvo on kantaluokka, mutta instanssissa saattaa olla jompi kumpi tämän luokan periytyvistä luokista riippuen instanssin luontitavasta. @returns Kapseli, joka sisältää osoittimen. */ _BuilderBase<ret,args>* getPtrCapsule() const { return ptrCapsule; } /** Tuhoaa instanssissa olevan funktiokapselin muistista. */ void release() { if( ptrCapsule ) delete ptrCapsule; ptrCapsule = 0; } /** Kopioi intanssille tiedot toisesta instanssista. @param other Instanssi, josta tiedot kopioidaan. @returns Kutsuvan instnssin viittaus, jotta operaattoria voidaan käyttää peräkkäin. */ FunctionPtr& operator= (const FunctionPtr& other) { release(); ptrCapsule = other.getPtrCapsule()->copy(); return *this; } /** Kopioi intanssille tiedot kapselista. @param other Kapseli, josta tiedot kopioidaan. @returns Kutsuvan instnssin viittaus, jotta operaattoria voidaan käyttää peräkkäin. */ FunctionPtr& operator= (_BuilderBase<ret,args>* capsule) { release(); ptrCapsule = capsule; return *this; } /** Kertoo, onko instanssiin asetettu funktio. @returns Palauttaa true, jos instanssi on tyhjä, false jos ei ole. */ bool isNull() const { return !ptrCapsule; } private: _BuilderBase<ret,args>* ptrCapsule; // funktio-osoittimen sisältävä luokka }; /** tämä luokka sisältää halutun tyyppisen funktion pointterin. @remarks pointterin voi luoda funktiosta, tai tietyn luokan instanssin jäsenfunktiosta. Luokka on malliluokka, joten siitä vuo luoda minkä tyyppisiä funktio-osoittimia tahansa. Funktiolla on paluu- arvo sekä YKSI (1) parametri, jotka käyttäjä saa itse valita instanssia luodessaan. Jäsenfunktion voi ottaa minkä tahansa luokan instanssista! @remarks Erikoistapaus void-tyyppiselle parametrille. */ template <typename ret> class FunctionPtr<ret, void> { public: typedef ret (*Pointer)(void); /** Luo tyhjän instanssin */ FunctionPtr() : ptrCapsule(0) { } /** Luo alustetun instanssin olemassa olevasta osoittimesta. @param ptr Osoitin funktioon. */ FunctionPtr( Pointer ptr ) { ptrCapsule = new _SingleBuilder<ret,void>(ptr); } /** Luo alustetun instanssin halutun luokan olion jäsenfunktioista. @param (ret (T::*f)(args)) osoitin luokan jäsenfunktioon @param instance Osoitin olioista, jonka funktio toteutetaan. */ template <class T> FunctionPtr(ret (T::*f)(void), T* instance) { ptrCapsule = new _MemberBuilder<ret,void,T>(f, instance); } /** Luo uuden instanssin käyttäen käyttäen kopioitavan luokan _BuilderBase-instanssia. @remarks Tämä ei ole tavalliselle käyttäjälle sillä kyseiset instanssit ovat vain FunctionPtr-luokan saavutettavissa. Tätä käytetäänkin vain kopiomuodostimessa FunctionPtr-luokan instansseille, joita käyttäjä voi käyttää. @param capsule Kapseli, joka sisältää toiminnallisuuden. */ FunctionPtr( _BuilderBase<ret,void>* capsule ) { ptrCapsule = capsule; } /** Luo uuden instanssin kopioiden sen annetusta instanssista. @remarks Luodun instanssin ()-operaattori tekee siis täysin samat asiat, mitä kopioitavan instanssin operaattori. @param other Instanssi, josta tiedot kopioidaan. */ FunctionPtr(const FunctionPtr& other) : ptrCapsule(0) { if( other.getPtrCapsule() ) ptrCapsule = other.getPtrCapsule()->copy(); } /** Tuhoaa instanssin ja vapauttaa mahdollisesti osoitinkapselin varaamaan vapaan muistin. */ ~FunctionPtr() { release(); } /** Suorittaa instanssissa olevan funktion. @remarks Sinun täytyy olla varma, että instanssissa on varmasti funktio-osoitin sillä tämä ei tarkasta sitä ja seuraukset huolimattomasta käytöstä voivat olla vakavat. @param a Halutun tyyppinen instanssi, jonka funktio ottaa parametrikseen. @returns Halutun tyyppisen instanssin, jonka funktio palauttaa. */ ret operator() () const { assert( ptrCapsule ); return (*ptrCapsule)(); } /** Palauttaa käyttäjälle instanssissa olevan kapselin osoitteen. @remarks Palautettava arvo on kantaluokka, mutta instanssissa saattaa olla jompi kumpi tämän luokan periytyvistä luokista riippuen instanssin luontitavasta. @returns Kapseli, joka sisältää osoittimen. */ _BuilderBase<ret,void>* getPtrCapsule() const { return ptrCapsule; } /** Tuhoaa instanssissa olevan funktiokapselin muistista. */ void release() { if( ptrCapsule ) delete ptrCapsule; ptrCapsule = 0; } /** Kopioi intanssille tiedot toisesta instanssista. @param other Instanssi, josta tiedot kopioidaan. @returns Kutsuvan instnssin viittaus, jotta operaattoria voidaan käyttää peräkkäin. */ FunctionPtr& operator= (const FunctionPtr& other) { release(); ptrCapsule = other.getPtrCapsule()->copy(); return *this; } /** Kopioi intanssille tiedot kapselista. @param other Kapseli, josta tiedot kopioidaan. @returns Kutsuvan instnssin viittaus, jotta operaattoria voidaan käyttää peräkkäin. */ FunctionPtr& operator= (_BuilderBase<ret,void>* capsule) { release(); ptrCapsule = capsule; return *this; } /** Kertoo, onko instanssiin asetettu funktio. @returns Palauttaa true, jos instanssi on tyhjä, false jos ei ole. */ bool isNull() const { return !ptrCapsule; } private: _BuilderBase<ret,void>* ptrCapsule; // funktio-osoittimen sisältävä luokka }; #endif
Esimerkki 1 - helppo
#include <string> #include <iostream> #include "FunctionPtr.h" bool writeMessageA(const std::string& m) { std::cout << "Hello " << m << "!\n"; return true; } bool writeMessageB(const std::string& m) { std::cout << "Bye bye " << m << "!\n"; return true; } typedef FunctionPtr<bool, const std::string&> Pointer; int main() { std::string name; std::cin >> name; Pointer ptr(writeMessageA); ptr( name ); ptr = Pointer(writeMessageB); ptr( name ); return 0; }
Esimerkki 2 - keskitaso
#include <string> #include <iostream> #include "FunctionPtr.h" bool writeMessageA(const std::string& m) { std::cout << "Hello " << m << "!\n"; return true; } bool writeMessageB(const std::string& m) { std::cout << "Bye bye " << m << "!\n"; return true; } typedef FunctionPtr<bool, const std::string&> Pointer; int main() { std::string name; std::cin >> name; // luodaan osoitintaulukko ;) Pointer ptrArray[2]; ptrArray[0] = Pointer(writeMessageA); ptrArray[1] = Pointer(writeMessageB); // annetaan käyttäjän valita viesti int select; std::cout << "Select message (0-1): "; std::cin >> select; if( select >= 0 && select < 2 ) ptrArray[select](name); return 0; }
Esimerkki 3 - vaikea
#include <string> #include <iostream> #include <map> #include "FunctionPtr.h" class Example { public: Example(const std::string& name) : mName(name) {} bool messageHello(const std::string& name) { std::cout << mName << " says 'hello!' to you " << name << ".\n"; return true; } bool messageBye(const std::string& name) { std::cout << mName << " says 'bye bye!' to you " << name << ".\n"; return true; } private: std::string mName; }; typedef FunctionPtr<bool, const std::string&> Pointer; typedef std::map<char, Pointer> PtrMap; int main() { std::string name; std::cin >> name; // luodaan yksi instanssi, jonka jäsenfunktioihin osoitetaan Example someone("Peter"); // luodaan kartta ja lisätään siihen muutama avain PtrMap keys; keys['1'] = Pointer(&Example::messageHello, &someone); keys['2'] = Pointer(&Example::messageBye, &someone); // annetaan käyttäjän valita viesti char select; std::cout << "Select message (1-2): "; std::cin >> select; // tulostetaan se - tarkistus on hyvin tärkeää, sillä jos avaimeksi annettaisiin // esimerkiksi '3', olisi osoitin NULL ja siitä ei hyvä seuraisi... // jos otat tarkistuksen pois, laukaistaan assertio null-osoittimen sattuessa if( !keys[select].isNull() ) keys[select]( name ); return 0; }
Aihe on jo aika vanha, joten et voi enää vastata siihen.