Moi taas.. Täl kertaa mul on ongelmia olioiden ja luokkien kans, siis leikitään vaikka et on Luokka1 ja Luokka2.
Luokka1 sisältää Luokka2:n olion testiolio ja funktion testifunktio. Luokka2 sisältää osoittimen tosoitin.
Yritän Luokka1:n konstruktorissa testiolion tosoitin:men osottamaan testifunktioon, eli koodina:
class Luokka2{ public: void (*tosoitin)(int); }; class Luokka1 { public: Luokka2 testiolio; void testifunktio(int joopajoo); Luokka1(); }; Luokka1 :: Luokka1(){ testiolio.tosoitin = testifunktio; } Luokka1 :: testifuntkio(int joopajoo){ int numero = 0; }
Virheilmotukseks tulee 'argument of type `void (Luokka1::)(int)' does not match `void (*)(int)''
Mikä mättää? :(
Mitähän tuolla olisi tarkoitus tehdä? :o
Kun jäsenfunktiota kutsutaan, sille annetaan parametriksi kyseisen olion osoite (this). Se on sinänsä aivan tavallinen parametri aivan samoin kuin int joopajoo
, se ei vain näy ohjelmoijalle samalla tavalla. (Ei nyt puututa pikkuseikkoihin siitä, missä parametrit asuvat, rekisterissä vai pinossa...) Funktio-osoittimessasi et ole määritellyt sellaista parametria, tuskinpa sellainen edes on mahdollista. Funktio-osoittimesi ei siis ole samaa tyyppiä kuin funktio, jota yrität siihen laittaa, koska siinä ei ole kyseistä parametria.
Testifunktion pitäis pystyy käyttää Luokka1:n jäsenmuuttujia, ja sitä pitäs pystyy osottaa Luokka2:n osotin.
Mitä siis pitää muuttaa?
Tarvitset tietenkin myös luokan ilmentymän, eihän mitään jäsenmuuttujia muuten ole.
#include <iostream> using namespace std; class A_luokka; class B_luokka; // Osoitin luokan A_luokka funktioon, // joka ottaa kaksi inttiä ja palauttaa intin typedef int (A_luokka::*A_funktio)(int luku, int toinen); class B_luokka { public: A_luokka *objekti; A_funktio funktio; int x, y; void kutsu() { cout << (objekti->*funktio)(x, y) << endl; } }; class A_luokka { public: int oma; int yhteenlasku(int a, int b) { return a + b + oma; } }; int main() { A_luokka a; B_luokka b; b.funktio = &A_luokka::yhteenlasku; b.objekti = &a; b.x = 1; b.y = 2; a.oma = 3; b.kutsu(); return 0; }
Näin se selvisi ilman aiempaa tietoa asiasta.
Oma viritykseni tästä ns. functor:ista (onko oikea termi? :O):
http://koti.mbnet.fi/masa_89/koodit/FunctionPtr.
Sisältää tietysti paljon virheitä, joita en ole huomannut... Pitäisi toimia kaikkien parametri-arvojen kanssa sekä kaikilla paluutyypeillä void:ia lukuun ottamatta.
Tällainen linkki muistui mieleen http://www.devmaster.net/forums/showthread.php?t=5884&highlight=function proxy
Testasin tota Metabolixin koodia. Se toimi hyvin. Sit koitin laittaa A_luokka:n ja B_luokka:n omiin tiedostoihin. En saanu toimii mitenkää. Mite siis saan ne luokat omiin .h ja .cpp tiedostoihin että toimiski viel?
Ennen B-luokan määrittelyä täytyy olla määritelty siinä käytetyt asiat, kuten nyt tuossa koodissa on. A-luokka taas ei tarvitse B-luokan tietoja, joten tilanne selviää helposti niin, että B-luokan otsikkoon liitetään A-luokan otsikko.
A_luokka.h:
#ifndef _A_luokka_H_ #define _A_luokka_H_ 1 class A_luokka; typedef int (A_luokka::*A_funktio)(int luku, int toinen); // Jos A-luokka tarvitsisi B-luokkaa, niin tässä kohti #include "B_luokka.h" class A_luokka { public: int oma; int yhteenlasku(int a, int b) { return a + b + oma; } }; #endif
B_luokka.h:
#ifndef _B_luokka_H_ #define _B_luokka_H_ 1 // Jos A-luokka tarvitsisi B-luokkaa, seuraava rivi olisi melkeinpä pakollinen. Nyt sillä ei ole niin merkitystä. class B_luokka; #include "A_luokka.h" #include <iostream> class B_luokka { public: A_luokka *objekti; A_funktio funktio; int x, y; void kutsu() { std::cout << (objekti->*funktio)(x, y) << std::endl; } }; #endif
main.cpp:
#include "A_luokka.h" #include "B_luokka.h" int main() { A_luokka a; B_luokka b; b.funktio = &A_luokka::yhteenlasku; b.objekti = &a; b.x = 1; b.y = 2; a.oma = 3; b.kutsu(); return 0; }
Jos A-luokkaan pitäisi saada B-luokka suoraan eikä osoittimena, joutuisi otsikoille tekemään mutkikkaamman rakenteen, jotta moneen kertaan liittäminenkään ei aiheuttaisi ongelmia.
Kyllä A-luokkaan pitäis saada B-luokan olio, eli miten se mutkikkaampi rakenne tehään? Tai jossain linkkiä?
MR.Coodari, voisitko tarkemmin kertoa, mitä oikein olet tekemässä?
Ei siinä mitään, kyllä tuohon kysymykseesikin pystyy vastaamaan ihan täsmällisesti, mutta kun se kysymys taustaa tietämättömälle kuulostaa vähän samalta kuin "miten naulaan vatupassilla?"
Oon tekemäs pientä tekstiseikkailua, ja siin mul on game-luokka jonne isken kaiken joka kuuluu peliin. Oon tehny menusysteemin sillee et mul on menu-luokka jossa on osoitin funktioon jolle annetaan valinnan numero parametrinä. Mulla on siis game-luokassa menu-olioita ja game-luokassa on funktiot johon menu-luokasta osotellaan. Semmosta teen eikä taho onnistuu.
Itse tekisin tuon yksinkertaisuuden nimissä siten, että menua luodessa sille annetaan vain näkyvä nimi ja toimintakoodi, jolla se sitten kutsuu game-luokan jotain yleistä menun käsittelijämetodia, kun menu valitaan. Funktioiden kutsu tapahtuisi silloin Game-luokan sisällä eikä mitään funktiopointtereita tarvittaisi. Syy tälläiseen ratkaisuun on osittain siinä, että en alkuunkaan tykkää C/C++:n funktiopointterisyntaksista ja osittain siinä, että tällöin Game-luokan metodeja ei tarvitse tehdä ulospäin näkyviksi.
Kai tuo funktiopointtereillakin onnistuu, jos vain määrittelyt on kunnossa. Jos pointterit luokan sisäisiin funktioihin tuottaa ylitsepääsemättömiä ongelmia, voi tietenkin tehdä luokan ulkopuolisia funktioita, jotka toimivat wrappereinä luokan sisäisiin funktioihin.
MR.Coodari, mistä se menusysteemi tietää kutsua noita menu-olioita? Arvaanko oikein, että game-olio jollain tapaa rekisteröi menu-olionsa menusysteemille ja menusysteemi sitten aikanaan kutsuu menu-olion funktiota? Onko game-luokassa monta menu-oliota vai onko menu-olioita vain yksi?
Ilmeisesti et halua, että menusysteemi tietää yhtään mitään game-oliosta suoraan ja siksi käytät menu-olioita? Tollaista kuviota nimitellään oliomaailmassa yleisemmin callback:iksi. Sitä nuo Mazzimonkin jutut näemmä edustavat (functor ei ihan taida tarkoittaa tätä).
Menusysteemi varmaankin toimii niin, että kun jokin valinta tehdään, systeemi kutsuu sille rekisteröidyn olion tiettyä funktiota? Meneekö homma menee niin, että menuvalinta kutsuu aina saman olion samaa funktiota mutta eri parametreilla riippuen valinnasta? Silloin yksinkertaisin tapa on, että game-olio rekisteröi itsensä menusysteemille ja menusysteemi sitten kutsuu game-luokan jotakin public-funktiota. Mutta tätä et ilmeisesti halua esmes siksi, että se kytkee game-luokan ja menusysteemin tiukasti toisiinsa?
Seuraava tapa voisi olla, että game-luokka periytetään jostakin menuaction-luokasta. Menuaction-luokassa on sitten pure virtual funktio, jota menusysteemi kutsuu ja tuon funktion toteutus tehdään game-luokassa. Noin se suora kytkentä menusysteemin ja game-luokan välillä vältetään, kun menusysteemi tunteekin vain menuaction-rajapinnan. Tässä voisi käyttää jotenkin private-perintääkin, siitä näkyy olleen juttua newseissä.
Jos menusysteemin on tarkoitus kutsua valinnasta riippuen eri funktioita, noita varten voi tehdä erilaisia menuaction-luokkia. Game-luokka johdetaan moniperinnällä niistä kaikista. Hankaluutta kyllä tulee, jos se kutsuttava pure virtual funktio on samanniminen noissa kaikissa eri menuaction-luokissa.
Enemmän joustavuutta saisi jalostamalla tuota Mazzimon koodia. Silloin ne menuoliot olisivatkin irrallisempia callback-otuksia, sillä ei niiden muutenkaan kannata olla game-luokan jäsenmuuttujia.
Toivottavasti tämä nyt vähän auttaa oikeaan suuntaan, kun tarkemmista yksityiskohdista ja tarpeista riippuen homman voi tehdä joko tsiljoonalla tai kiljoonalla eri tavalla, joista sitten varmaan öpaut kaksi on kumminkaan järkeviä. :-)
Kyllä.. Kyllä.. Callback:iksi tuota systeemiä taidetaan kutsua, mutta functoreita nuo kutsuvat oliot taitavat olla. En ole varma, ei siitä sen enempää..
Mutta mutta, kuten koo jo mainitsikin tuossa, ehdottomasti helpoin tapa toteuttaa tämä ns. callback-systeemi, on käyttää polymorfismia. Itsekin käytän tätä systeemiä, jos vain mahdollista. Omasta mielestäni tilakone on tällä tavalla erittäin kätevä tehdä. Viestistäni tulee hieman pitkä, mutta toivon asian tulevan selväksi. Selvitän ensiksi tuon polymorfismin näyttämällä, miten olen tilakoneeni rakentanut.
Eli ensiksi minulla abstrakti luokka, joka sisältää tarvittavat metodit tilan hallintaan:
#pragma once // StateTemplate.h /* 24.2.2007 - Tiedosto luotu. */ /** Pohja tapahtuma-kontrollerin jäsenille. @remars Tämä luokka on tarkoitettu periytettäväksi eri ohjelman tiloille. Tämän luokan osoittimia on StateManager-luokan instanssissa ja ne on merkitty tila-avaimen (String) taakse. Tila-elementtien toiminta on seuraava: tilakontrollerilla on avain, jonka tilaa se käy selvittä- mään. Tilakontrolleri hakee tilan tämän avaimen perusteella ja alkaa suorittamaan tilan start-metodia. Tätä metodia suoritetaan niin kauan, kunnes se palauttaa FALSE. Sitten siirrytään suorittamaan saman tila- elementin loop-metodia, lopuksi end-metodia. Näissä kahdessa on sama ehto kuin start-metodillakin. Kun end on palauttanut FALSE, katsoo tilakontrolleri seuraavalla loopilla nykyisen avaimensa perusteella seuraavan tila-elementin, jota käydään toteuttamaan. @notes Luokka on abstrakti, muista periyttää se ylikirjoittaa virtuaalimetodit. Tämän jälkeen lisää yksi luokan instanssi tilakontrolleriin valmiiksi käytettäväksi. */ class StateTemplate { public: virtual ~StateTemplate() {} /** Ensimmäinen kohta, joka suoritetaan. */ virtual bool start() = 0; /** Toinen kohta, joka suoritetaan. */ virtual bool loop() = 0; /** Viimeinen kohta, joka suoritetaan. */ virtual bool end() = 0; };
Eli systeemi on hyvin yksinkertainen. Tuon luokan osoittimia varastoidaan itse tilakontrolleriin ja start-loop-end -metodeita kutsutaan, kun tila saadaan voimaan.
Sitten tarvitaan manageri, joka huolehtii varastoiduista tila-elementeistä. Systeemissäni tilat luodaan ohjelman alussa ja ne poistetaan ohjelman lopussa.
#pragma once // StateManager.h /* 24.2.2007 - Tiedosto luotu. */ #include "BaseItems.h" // esitellään class StateTemplate; /** Ohjelman tilaa valvova ja nykyisen tilan toteuttava luokka. @remarks Ohjelman FrameListener kutsuu metodissaan "frameStarted" tämän luokan globaalia singleton-instanssia ja siitä update-metodia. Update-metodi valvoo nykyistä tilaa ja toteuttaa nykyisen tilan toiminnot, jotka instanssiin on määritetty. Intanssin seuraavan tilan saa määritettyä setState-metodilla. Tämä tila tulee voimaan heti, kun nykyinen on lopettanut toimintansa. Eri tiloja saa asetettua avainten taakse addState-metodilla. @notes Tämän luokan instanssi luodaan createScene-funktiossa, se tuhotaan destroyScene-funktiossa. Luokka on globaali singletonluokka. */ class StateManager : public Ogre::Singleton<StateManager> { public: /** Luo uuden alustetun instanssin. */ StateManager(); /** Tuhoaa instanssin. @notes Tuhoaa instanssiin dynaamisesti luodut tila-elementit niiden osoittimiensa perusteella. */ ~StateManager(); /** Päivittää tilan. @notes Tätä metodia tulee kutsua joka framella. @return TRUE, jos ei ole annettu lopetustilaa, FALSE jos ohjelma halutaan lopettaa. */ bool update(); /** Lisää manageriin uuden tilan. @param key Avain, jonka taakse uusi tila luodaan. @param element Tila-elementti, joka tulee luoda dynaamisesti ja asettaa avaimen taakse. @notes Lisätty tila-elementti tuhotaan automaattisesti tuhoajafunktiossa. Tämä metodi heittää poikkeuksen, jos avaimen taakse on jo määritetty tilaelementti. Avain nimeltä "StateShutDown" on varattu ja sitä ei saa antaa avaimeksi. Tässä tapauksessa heitetään poikkeus. */ void addState(const String& key, StateTemplate* element); /** Asettaa managerin seuraavan tilan. @param key Avain, jonka tila halutaan asettaa. @notes Metodi heittää poikkeuksen, jos asetettavalle tilalle ei löydy elementtiä. Jos löytyy, tila tulee voimaan vasta, kun nykyinen tila on saatu päätökseen. Tämä metodi heittää poikkeuksen, jos avaimen taakse ei ole määritetty tilaelementtiä. */ void setState(const String& key); /** Pakottaa nykyisen tilan loppumaan. @remarks Nykyinen tila suljetaan heti paikalla ja seuraava tila tulee voimaan heti seuraavalla framella. @notes Metodi on hyvin vaarallinen, koska tilan end-metodia ei kutsuta, joten tilan mahdollisesti varaamaa dynaamista muistia ei vapauteta. Tätä metodia tulee käyttää vain, jos tietää mitä tekee. */ void forceStateEnd(); private: /** Kartta tila-elementeille. */ typedef std::map<String, StateTemplate*> StateMap; StateMap mStates; // kartta asetetuista tiloista String mCurStateName; // viimeiseksi asetetun tilan nimi int mStatePos; // tilan suorituskohta StateTemplate* mCurState; // nykyisen tila-elementin osoitin };
Katso erityisesti rivillä 96 oleva typedef ja 99 rivillä oleva jäsenmuuttuja. mStates-jäsenmuuttujaan sisällytetään kaikki ohjelman tilat käyttäen tilan avaimena merkkijonoa (esimerkiksi: "StateMenu"). Vain yksi merkkijono on varattu, "StateShutDown", joka sammuttaa ohjelman.
Seuraavassa näet tilamanagerin toteutuksen:
#include "StateManager.h" #include "StateTemplate.h" StateManager* Ogre::Singleton<StateManager>::ms_Singleton = 0; StateManager::StateManager() : mCurState(0), mStatePos(0), mCurStateName("Undefined") { } StateManager::~StateManager() { for( StateMap::iterator it = mStates.begin() ; it != mStates.end() ; it++ ) { if( it->second ) { delete it->second; } } mStates.clear(); } bool StateManager::update() { if( mCurState ) { switch( mStatePos ) { case 0: if( !mCurState->start() ) mStatePos++; break; case 1: if( !mCurState->loop() ) mStatePos++; break; case 2: if( !mCurState->end() ) forceStateEnd(); break; }; } else { if( mCurStateName == "StateShutDown" ) return false; if( !mStates.count( mCurStateName ) ) OGRE_EXCEPT(0, "State not defined behind key \""+mCurStateName+"\"", "StateManager::update"); mCurState = mStates[mCurStateName]; } return true; } void StateManager::addState(const Ogre::String &key, StateTemplate *element) { if( mStates.count( key ) ) OGRE_EXCEPT(0, "State already defined behind key \""+key+"\".", "StateManager::addState"); mStates[key] = element; } void StateManager::setState(const Ogre::String &key) { mCurStateName = key; if( key == "StateShutDown" ) return; if( !mStates.count( key ) ) OGRE_EXCEPT(0, "State not defined behind key \""+key+"\".", "StateManager::setState"); } void StateManager::forceStateEnd() { mStatePos = 0; mCurState = 0; }
Kun ohjelmaan halutaan luoda tila odottamaan vuoroaan, sille annetaan tunnus josta sitä voi kutsua. Lisäksi tilalla tulee olla myös osoitin olioon, jossa tila tullaan toteuttamaan. Kun katsot metodia StateManager::update, huomaat sen kutsuvan sokeasti nykyistä tilaelementtiä. StateManager ei tiedä, mitä tilaelementti ohjelmaan tekee, se kutsuu elementin metodeita sokeasti, vaikka tilaelementti potkisi mummoja perseelle minkä ehtii. StateManager::update-metodia kutsutaan yhden kerran framessa, joten tilaa päivitetään koko ajan. StateManager tietää vain sen, että kun tilaelementti palauttaa FALSE, on se tehnyt haluamansa. Tämän jälkeen manageri alkaa etsimään uutta tilaa, jota voisi toteuttaa. Tilaelementit voivat tehdä IHAN MITÄ TAHANSA, käyttäjä voi itse päättää siitä. Seuraavassa esimerkki siitä, miten tiloja voi luoda erilaisia:
#pragma once // GameState.h #include "StateTemplate.h" #include "StateManager.h" #include "MapManager.h" class StateGame : public StateTemplate { public: virtual bool start() { MapManager::getSingleton().loadMap("Nemeko"); return false; } virtual bool loop() { bool state = true; // PÄIVITETÄÄN PELI if( vihu_osuu_pelaajaan ) state = false; return state; } virtual bool end() { MapManager::getSingleton().destroyMap(); StateManager::getSingleton().setState("StateShutDown"); return false; } }; class StateMenu : public StateTemplate { public: virtual bool start() { // ladataan menu-elementit return false; } virtual bool loop() { bool state = true; if( start_button_cliked ) { state = false; StateManager::getSingleton().setState("StateGame"); } if( quit_button_cliced ) { state = false; StateManager::getSingleton().setState("StateShutDown"); } return state; } virtual bool end() { // tuhotaan menu-elementit return false; } };
Sitten vielä ohjelman alussa lisään tilat tilamanageriin ja käynnistän ensimmäisen tilan, joka on menu.
StateManager& sm = StateManager::getSingleton(); sm.addState( "StateMenu", new StateMenu() ); sm.addState( "StateGame", new StateGame() ); sm.setState( "StateMenu" );
Toivottavasti tajusit edes hieman, miten käyttää polymorfismia hyväksesi joustavaa koodia tehdessäsi.
Ja sitten seuraava eli functorit. Functoreita kannattaa käyttää vain, jos haluaa erittäin joustavaa koodia ja kutsuttava funktio saattaa todellakin tulla mistä tahansa, joko tavallisesta funktiosta tai sitten tietyn olion jäsenfunktiosta. Itse olen toteuttanut esimerkiksi hiiren ja näppäimistön tällä tavalla. Jokaiselle syötteelle asetetaan oma tapahtuma, joka laukaistaan kyseisen syötteen tullessa.
Ensiksi tarvitaan instanssin määrittely:
http://koti.mbnet.fi/masa_89/koodit/BaseMouse.h
Kiinnitä tässä erityisesti huomiota riveihin
typedef FunctionPtr<bool, const EventArgs&> EventPtr; // ja typedef std::map<String, EventPtr> EventMap;
sekä komentoon setEvent(const String& type, EventPtr ptr). Kyseisellä komennolla lisätään hiireen tapahtuma, joka laukaistaan tarvittaessa. Seuraavassa hiiren toteutus:
http://koti.mbnet.fi/masa_89/koodit/BaseMouse.cpp
Näet Mouse::clearEvent-metodista, miten jäsenfunktioon voidaan osoittaa. On myös erittäin suositeltavaa, että teet struktuurin parametreistä, jotka välität. Esimerkissäni välitettävä struktuuri on Mouse::EventArgs, joka sisältää hiiren sijainnin sekä liikkeen viime framen aikana. Se on oikeastaan kaikki mitä käyttäjä tarvitsee.
Mistä hiiri sitten tietää, milloin käyttäjä on painanut jotain näppäintä? Huomasit varmasti, että olin määrittänyt hiirelle ystävyysluokaksi BaseFrameListener:in. Tämä luokka välittää hiirelle tapahtuneet syötteet joiden pohjalta hiiri osaa laukaista sille viritetut tapahtumat.
http://koti.mbnet.fi/masa_89/koodit/BaseFrameListenerMouseInput.cpp
Tuosta löytyy koodi, jolla syötteet kommunikoivat hiiren kanssa. En kommentoi sitä enempää. Tuon lisäksi hiiri kutsuu joka framella update-metodia, jotta alas painetut näppäimet saadaan päivitetyksi. Jotta saat selvää functorien kutsumis-metodeista, kannattaa katsoa BaseFrameListener::mouseMoved-metodi tarkkaan, rivi m.myEvents[Mouse::MouseMoved](m.myAttributes); on kaiken avain.
Jeps. Eli funktioita saa välitettyä hiirelle seuraavalla tavalla:
bool ammu(const Mouse::EventArgs& e) { // pum pum return true; } class Ukko { public: bool hyppelehdi(const Mouse::EventArgs& e) { // hop hop return true; } }; // koodia.... Ukko* u = new Ukko(); Mouse& m = Mouse::getSingleton(); m.setEvent(Mouse::MouseHit[0], Mouse::EventPtr(ammu)); m.setEvent(Mouse::MouseHit[1], Mouse::EventPtr(&Ukko::hyppelehdi, u);
Aina kun hiiren vasenta näppäintä klikataan, ammutaan. Aina kun oikeaa näppäintä klikataan, ukko hyppii.
Tässäpä oli pieni esimerkki functoreista.
Nää functorihommat taitaa olla just sitä mitä hain, kiitos :)
Aihe on jo aika vanha, joten et voi enää vastata siihen.