[Megoldva:] Függvény átadása paraméterként

 ( pelz | 2008. augusztus 8., péntek - 20:08 )

Sziasztok!

Írok egy progit, amiben azt vettem észre, hogy nagyon sok teljesen hasonló függvényt kellett írnom. Ezeken a hasonló függvényeken belül csak kis részletek térnek el. Nagyjából így néznek ki:

void fv1(...) {
  // fájl beolvasás soronként

  // a sor manipulálása 1

  // kiírás új fájlba
}

void fv2(...) {
  // fájl beolvasás soronként

  // a sor manipulálása 2

  // kiírás új fájlba
}

void fv3(...) {
  // fájl beolvasás soronként

  // a sor manipulálása 3

  // kiírás új fájlba
}

// stb...

Nyilvánvaló, hogy sokkal jobb lenne, ha csak a manipulálás függvényeket írnám meg külön-külön, és a fv1(), fv2() ... fvX() helyett, csak egy fvGeneral() lenne, aminek az egyik paramétere egy manipulal() függvény lenne. És ezek után a függvények listája a következőképpen nézhetne ki:

void fvGeneral(..., FunctionType manipulal(...)) {
  // fájl beolvasás soronként

  outString = manipulal(...);

  // "outString" kiírása új fájlba
}

std::string manipulal1(std::string in) {
  std::string out;
  // "in" string manipulációja
  return out;
}

std::string manipulal2(std::string in) {
  std::string out;
  // "in" string manipulációja
  return out;
}

std::string manipulal3(std::string in) {
  std::string out;
  // "in" string manipulációja
  return out;
}

int main() {
  // a fő program egyéb részei

  fvGeneral(..., manipulal1());
  fvGeneral(..., manipulal2());
  fvGeneral(..., manipulal3());
}

Előre is köszönöm a segítséget!

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Nem tudom milyen manipulasokrol van szo, de igy elsore nem tunik memora ill. CPU hatekonynak a terv.

Nem egyszerubb fogni a jo oreg switch case -t ? Valahol ugy is kell ilyen a programba szerintem.

Én is pont ezt akartam javasolni. Vagy a generális függvénybe kellene egy olyan szerkezet, ami egy paramétertől függően meghívja az egyedi függvényeket, vagy eleve csak egy függvényt kellene írni.

---
Science for fun...

Valami ilyesmire gondolsz?

void fvGeneral(..., int man) {
  // fájl beolvasás soronként

  switch (man) {
    case 1:
       outString = manipulal1();
       break;
    case 2:
       outString = manipulal2();
       break;
    case 3:
       outString = manipulal3();
       break;
  }

  // "outString" kiírása új fájlba
}

Ez sem rossz! De nagyon érdekelne a függvény-paraméteres megoldás is!

Én csak azt nem értem, hogy miután leírtad a megoldást mi a kérdés?

Az, hogy konkrétan milyen szintaxissal kell függvényt átadni paraméterként?

Így elsőre ezt adja ki a "c function pointer" keresőkifejezés: http://www.newty.de/fpt/intro.html

Amit írtál az a C-s megoldás, csak szintaktikailag helytelenül leírva, de a fenti link alapján tudod javítani.

Mivel azonban C++-ban dolgozol (std::string C-ben nincs ugye) szebb lenne ha objecktum orientált megoldást készítenél.

Amit csinálsz kísértetiesen hasonlít erre a tervezési mintára: http://en.wikipedia.org/wiki/Command_pattern#Example_.28C.2B.2B.29

Annyi különbséggel, hogy neked nem elég egy interfész, hanem kell egy absztrakt ősosztály, amiben az execute metódusban benne van a fájl nyitás és zárás és közötte egy absztrakt metódusnak a meghívása, amit minden leszármazott másképp implementál.

Szerk.: Ez a minta még inkább passzol arra amit csinálni akarsz: http://en.wikipedia.org/wiki/Template_method_pattern

Nagyon köszönöm!

Ez az absztrakt ősosztály tetszik. Megpróbálom az alapján elkészíteni a megoldást.

#include <stdio.h>
#include <stdlib.h>

char *manipulal1(char *data) {
        printf("manipulal1\n");
}

char *manipulal2(char *data) {
        printf("manipulal2\n");
}

char *manipulal3(char *data) {
        printf("manipulal3\n");
}

void feldolgoz(char *filename, char *manipfv(char *)) {
        printf("feldolgoz\n");
        char *data, *out;
        // open
        // while ...
        // data = gets(...)
        out = manipfv(data);
        // close
}

int main(int argc, char **args) {
        feldolgoz("filename.txt", manipulal1);
        feldolgoz("filename.txt", manipulal3);
        feldolgoz("filename.txt", manipulal2);
}

Kösz!

(Akkor ez a klasszikus C-beli megodás.)

Igen. Csak arra vigyázz, hogy a char *-gal átadott paramétereket in-place módosítod, tehát a bemeneti változód is meg fog változni, ha szövegbe belemódosítasz valamivel. Ha ezt nem akarod, akkor strcpy-vel le kell másolnod, viszont ez meg a teljesítményt csökkenti.

A "teljesítmény" az én esetemben nem kritikus tényező. Az hogy 2 másodperc alatt fut-e le a program vagy 3, nem számít. Kösz!

Megoldás 1 (STL like):

class Manipulal1
{
public:
   string operator() (const string& in)
   { //do it}
};

class Manipulal2
{
public:
   string operator() (const string& in)
   { //do it}
};

...

template <class Func>
void fvGeneral(..., Func f)
{
    // fájl beolvasás soronként

    outString = f(inString);

    // "outString" kiírása új fájlba
}

int main()
{
   fvGeneral(..., Manipulal1());
}

Előny: gyorsabb lehet (jobb optimalizációs lehetősége van a fordítónak)
Hátrány: nagyobb kódméret.
Egyébként leginkább a C-s fv-mutatós megoldással rokon.

Megoldás 2 (OOP):

class General
{
public:
  void doIt(...);
  virtual ~General() {}
private:
  virtual string manipulal(const string& in) =0;
}

void General::doIt(...)
{
    // fájl beolvasás soronként

    outString = manipulal(inString);

    // "outString" kiírása új fájlba
}

class Manipulal1 : public General
{
  virtual string manipulal(const string& in);
}

string Manipulal1::manipulal(const string& in)
{
 //do it
}
...

int main()
{
  Manipulal1 man;
  man.doIt(...);
}

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Köszi a segítséget!

A második megoldásban egyetlen dolgot nem értek:

Idézet:
virtual string manipulal(const string& in) =0;

Az "=0" mit csinál ott a sor végén?

Azt mondja meg, hogy az eredeti class-ban nem adod meg a fuggveny torzset, mert nem lenne ertelme. Igy persze az eredetit peldanyositani sem lehet.
Miutan orokoltetsz belole egy masik osztalyt, es felulirod az eredeti metodust, mar peldanyosithato.

C-ben amugy a korabban mutatott fuggvenypointerekkel csinaltak meg a konyvtari qsort-ot altalanosra. (ld. Xanco megoldasat)

----
I have a solaris box that does nothing.. its pretty good at it aswell. - besok, deviantart
honlapkészítés

Már írom ezt az OOP-s verziót. Gondom is volt a virtuális függvénnyel, mert kihagytam belőle az "=0" -t. Bár a fordítás lezajlott, de mégis fura üzeneteket írt ki.

Kösz, még egyszer!

Ez a 0 gyakorlatilag egy NULL erteknek felel meg, nem?
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Nem, több okból:
1. C++-ban nincs NULL :)
"#define NULL (void*)0" még lehetne, de többet árt, mint használ...
El kell felejteni. (C++0x-ben (köv. szabvány) lesz nullptr.)

2. Ez csak egy jelölés, annyit jelent, hogy nincs definiálva. Csak az "=0" elfogadott.
(C++0x-ben lesz még "= delete", illetve "= default" is...)

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Felcsigaztal: mi a gond a "#define NULL (void*)0" kifejezessel amugy? (oke, ez esetben nem hasznalhato, megertettem. De mi gond van vele meg?)
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Leginkább az, hogy ez:

#define NULL ((void*)0)

int main()
{
  int* p1;
  p1=NULL;

  return 0;
}

ezt váltja ki a G++-ból:

test.cpp: In function `int main()':
test.cpp:6: error: invalid conversion from `void*' to `int*'

Ami egyébként a szabványos viselkedés.
Ha osztályokról van szó, szülőosztály A, leszármazott B, akkor teljesen logikus, hogy egy A* mutatónak adhatunk B* értéket, viszont fordítva nem.
Igaz, nincs ilyen viszony a void* és az int* között, de azért a void* valahogy általánosabb, mutathat bármire, ezért a típusbiztonság miatt logikus így.

Ezek szerint ezt kéne írni:

  p1=static_cast<int*>(NULL);

Ezt ugye írja akinek két anyja van. :)
(A "p1=(int*)NULL" jellegű dolgok nem a C++ részei, C örökség, nem illik ezeket használni...)

Így tehát a 0, azon kívül, hogy egy integer, még implicite cast-olódik minden mutatótípusra is, és azt jelenti amit várunk, akkor is, ha azon az architektúrán a null mutató bitmintája nem csupa 0.

Tehát C++-ban szokták ezt:

#define NULL 0

Csak ezt meg ugye minek, illetve...

Ezek után minek a nullptr a c++0x-be?

#define NULL 0

void foo(char *); //1
void foo(int); //2

int main()
{
   char* str="test";
   char* p=0;

   foo(str);  //remek: 1 fut
   foo(p);    //meg mindig jo: 1 fut
   foo(NULL); //biztos nem ezt akartad: 2 fut
   foo(0);    //most melyiket is akartad: 2 fut
}

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Oke, megertettem. Bar en minden szivfajdalom nelkul hasznaltam eddig a (int*) NULL alaku kifejezeseket, ezek szerint ennek hasznalata kerulendo. Kar, hogy a C++ nem definialja maganak felul a NULL erteket, ha mar ennyi gond van vele amugy is. A fordito ugyis tudja, mikor fordit C++ kodot, es mikor C-t.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.