Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Funktion parametriksi funktio

Sivun loppuun

punppis [02.07.2008 05:49:26]

#

Yritän tehdä SDL:llä tälläistä nappulasysteemiä peliini, ja nyt on tullut pulma johon en ole löytänyt vastausta.

Minulla on siis luokka noille nappuloille, luokka sisältää muuttujat sijainnille jne., mutta nyt pitäisi saada tallennettua sinne myös onclick-ominaisuus, eli funktio, jota kutsutaan kun nappulaa on painettu. Eli mitä siihen konstruktoriin pitäisi laittaa, että saa moisen koodin toimimaan:

// Eli ensiksi luodaan funktio, joka aloittaa pelin
void start() {
    // peli alkoi...
}

class Nappula {
    int x, y, w, h;
    // mitä tähän?
};

// Sitten tehdään nappula, jota painamalla peli aloitetaan. Eli ensimmäiset neljä parametriä ei ole olennaisia, vaan tuo viides. Miten teen sellaisen konstruktorin/luokan, että voin kirjoittaa vaan suoraan funktion nimen parametriksi, ja se ajetaan esimerkiksi komennolla aloitus.onclick();
Nappula aloitus = {x, y, w, h, start};

Blaze [02.07.2008 07:48:10]

#

Vastaus on funktiopointterit.

#include <stdio.h>

struct palle {
	int x;
	int y;
	void (*fp)(int); /* funktiopointteri */
};

void funkkari(int param) {
	printf("haha%d\n", param);
}

int main(int argc, char* argv[]) {
	struct palle p;
	p.fp = &funkkari; /* annetaan pointterille arvoksi funkkarin osote */
	(*p.fp)(15);      /* kutsutaan pointterin osottamaa funktiota */
	return 0;
}

Huomaa sitten, että funktiopointteri voi osottaa aina vaan yhentyyppisiin funktioihin, eli tuossa mun esimerkissä sen pitää aina palauttaa void ja ottaa yks int-tyyppinen parametri.

punppis [02.07.2008 09:49:43]

#

Koitetaan jos tosta sais väännettyä jotain.

Metabolix [02.07.2008 09:58:41]

#

Jos funktio-osoittimen tyypin syntaksi tuntuu hankalalta, tämä voi auttaa:

int kerro(int luku, int toinen) {
  return luku * toinen;
}

// Tyyppi määritellään aivan kuten funktiokin, mutta alussa on typedef
typedef int kahden_luvun_funktio(int a, int b);

// Osoittimia voi nyt luoda tähän tyyppiin:
kahden_luvun_funktio *osoitin = kerro;

printf("2 * 10 = %d\n", osoitin(2, 10));

Sijoituksessa ei tarvita Blazen käyttämää &-merkkiä eikä kutsussa *-merkkiä, mutta ei se väärinkään ole.

os [02.07.2008 18:56:53]

#

Kannattaa myös muistaa, että funktiopointtereiden käyttö on yleisesti ottaen huonoa ohjelmointia ja sillä ainoastaan hankaloittaa omaa elämäänsä. Kannattaa miettiä, onko oikeasti jotakin hyvää syytä olla ratkaisematta tuota ongelmaa esimerkiksi näin (C):

struct Nappula {
   int x, y, w, h;
   unsigned tyyppi;
};

void nappulaaPainettu(struct Nappula n) {
   switch(n.tyyppi) {
     case ALOITUSNAPPULA: aloita(); break;
     case LOPETUSNAPPULA: lopeta(); break;
     case JOKUMUUNAPPULA: printf("koordinaatit: %d %d\n", n.x, n.y); break;
   }
}

// ...

unsigned i;
for(i=0; i<nappuloiden_maara; i++) {
   struct Nappula n = nappulat[i];
   if(hiiri_x > n.x && hiiri_x < n.x+n.w && hiiri_y > n.y && hiiri_y < n.y+n.h)
      nappulaaPainettu(n);
}

tai näin (C++):

class Nappula {
private:
   int x, y, w, h;

public:
   Nappula(int X, int Y, int W, int H) : x(X),y(Y),w(W),h(H) {}

   bool osui(int hiiri_x, int hiiri_y) const {
     return hiiri_x > x && hiiri_x < x+w && hiiri_y > y && hiiri_y < y+h;
   }

   virtual void paina() = 0;

};

class Aloitusnappula : public Nappula
{
public:
    Aloitusnappula(int X, int Y, int W, int H) : Nappula(X,Y,W,H) {}
    void paina() { aloita(); }
};

// ... muut nappulat...


// ...
for(std::vector<Nappula*>::iterator i = nappulat.begin();
    i != nappulat.end(); i++)
  if((**i).osui(hiiri_x, hiiri_y)) (**i).paina();

Pekka Karjalainen [02.07.2008 21:04:42]

#

os kirjoitti:

Kannattaa myös muistaa, että funktiopointtereiden käyttö on yleisesti ottaen huonoa ohjelmointia ja sillä ainoastaan hankaloittaa omaa elämäänsä.

Tällaista väitettä en olekaan kuullut ennen. Mistä tälle löytäisi jonkinlaisia perusteita?

os [02.07.2008 22:12:33]

#

Googlen ensimmäinen tulos hakusanalla 'function pointer' kirjoitti:

But keep in mind: Always ask yourself if you really need a function pointer. It's nice to realize one's own late-binding but to use the existing structures of C++ may make your code more readable and clear.

No, eihän se ihan totta ole, että funktiopointterit olisivat välttämättä huonoa ohjelmointia.

Monessa tilanteessa vaan pääsee paljon helpommalla, kun "siirtää switch-rakenteen toiseen paikkaan". Ohjelma toimii arvattavammin ja sitä on helpompi debugata. C:ssä tietenkin funktiopointterit ovat välttämätön työkalu yleiskäyttöisiä kirjastoja tehtäessä (esim standardifunktio qsort).

Olio-ohjelmoinnissa funktiopointtereiden toiminnallisuus korvataan abstrakteilla luokilla/rajapinnoilla, jolloin kieli piilottaa funktiopointterit (C++:ssa käytännössä virtuaalitauluun). Tällöin ohjelma noudattaa paremmin olio-ohjelmoinnin mallia ja on rakenteellisesti selkeämpi. Rajapintaluokkien avulla on helppo kontrolloida sitä, mitä osaa ohjelmasta voidaan käyttää mistäkin paikasta. Jos luokkaa ei tietoisesti peritä asianmukaisesta rajapinnasta, sen ei voi väittää implementoivan rajapinnan toiminnallisuutta. qsortille voi syöttää johonkin aivan muuhun tarkoitetun (kuitenkin oikeantyyppisen) funktion, eikä kääntäjä valita mitään.

punppis [03.07.2008 01:13:48]

#

Joo huomasin tuon ensimmäisen postauksen jälkeen, että nuo funktiopointterit eivät oikein ole sitä mitä haen, ja ne myös näyttävät erittäin hankalilta. Tein siis nappulalle vain nimen, jonka mukaan teinkin juuri kuin os ehdotti.

Pekka Karjalainen [03.07.2008 08:35:41]

#

os kirjoitti:

No, eihän se ihan totta ole, että funktiopointterit olisivat välttämättä huonoa ohjelmointia.

Kiitos, että siirryit pelottelulinjasta selityslinjalle. Se on paljon mukavampaa lukijoista. :)

lainaus:

Jos luokkaa ei tietoisesti peritä asianmukaisesta rajapinnasta, sen ei voi väittää implementoivan rajapinnan toiminnallisuutta. qsortille voi syöttää johonkin aivan muuhun tarkoitetun (kuitenkin oikeantyyppisen) funktion, eikä kääntäjä valita mitään.

Tämä ei ole ihan johdonmukaista. Kääntäjä ei yleensä osaa tarkistaa, että perintä on tehty oikein tai että se noudattaa Liskovin substituutioperiaatetta tai mitä muuta vain voisi haluta. Pitää siis osata tehdä oikein itse, eikä luottaa automaattisiin tarkistuksiin.

Tämähän on juuri sama asia kuin qsortin funktio-osoitinparametrin kanssa tai minkä tahansa callbackin ottavan funktion kanssa. Pitää tietää, mitä sääntöjä noudattaa. Jos haluaisit esittää argumentin, että olio-ohjelmointi on tässä parempaa, pitäisi näyttää selvästi, millä tavalla se on parempaa. Ei ole rehellistä todeta, että jos funktiopointtereiden kanssa tulee virheitä (mitä tulee kaikilla niitä käyttävillä), vika on pointtereissa, ja jos olio-ohjelmoinnissa tulee virheitä (mitä tulee kaikilla...), vika on ohjelmoijassa. Se olisi vain propagandaa.

Ehkä on parasta jättää homma tähän. En aio väittää, että olio-ohjelmointi olisi huonompaa kuin funktio-osoittimia käyttävä C-tyyli. En ole sitä mieltä. Yritän sanoa sitä lukijoille, että ottakaa aina itse selvää ja miettikää, kun joku yrittää kieltää jonkin tekniikan. Punppis mietti asiaa, ja totesi, että hänelle nyt os:n ehdottama tapa on parempi. Hyvä niin.

Metabolix [03.07.2008 09:22:29]

#

Kieltämättä esimerkiksi juuri tässä tapauksessa yksi funktio switch-rakenteen kanssa on turvallisempi kuin funktio-osoittimet. Sen sijaan esimerkiksi käyttöjärjestelmäohjelmoinnissa tuo ratkaisu ei yleensä tule kysymykseenkään, ja C:ssä ainoaksi mielekkääksi vaihtoehdoksi jäävät juuri osoittimet. Kyseessä on tällöinkin aivan sama asia kuin C++:n abstrakteissa rajapinnoissa, ja riskit ovat samat. Siinä missä C-ohjelmoija voi sijoittaa osoittimeensa aivan väärän funktion, C++-ohjelmoija voi periyttää ja implementoida aivan vääränlaisen objektin. Molemmat ovat, kuten Pekka Karjalainen vihjasi, ohjelmoijan omia virheitä.

Todelliseksi käytännön eroksi jää, että funktio-osoitinta on mahdollista muuttaa kesken ohjelman, kun taas luokalla tämä ei oikein onnistu ilman abstraktia funktioluokkaa, josta periytetään funktio-objekteja... Tietyissä tilanteissa funktio-osoittimet voivat kuin voivatkin helpottaa elämää.

Kaikkein hienointahan on osata käyttää kaikkia mainittuja menetelmiä niille sopivissa paikoissa.


Sivun alkuun

Vastaus

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

Tietoa sivustosta