[linux-l] Function Pointer in C (was: java Klotz am Bein ?)

Steffen Dettmer steffen at dett.de
Sa Mai 28 03:18:57 CEST 2005


* Rocco Rutte wrote on Wed, May 18, 2005 at 20:00 +0000:
> >wie machst Das das in C eleganter?
> 
> struct foo {
>  char* key;
>  int (*handle) (char* key, list_t* args, unsigned long data);
>  unsigned long data;
> };

Prima Ansatz! 

(bitte keinen Compilertest machen, Syntax ist bestimmt überall falsch
:-)).


Ich glaub, so hat man OO mal erfunden...

... denn wenn man das jetzt umschreibt:

struct foo {
   char* key;
   int (*handle) (char* key, list_t* args, unsigned long data);
   unsigned long data;
};

dann key und data nicht nochmal übergibt, sondern struct foo*, also:

struct foo;
struct foo {
   int (*handle) (struct foo*, list_t* args);
   char* key;
   unsigned long data;
};

vielleicht noch bissel was umbennet:

class foo;
class foo {
   int (*handle) (struct foo*, list_t* args);
   char* key;
   unsigned long data;
};

also in lesbarer:

struct foo;
typedef int (*HandleMethod) (struct foo *this, list_t* args);
struct foo {
   HandleMethod handleMethod;
   char* key;
   unsigned long data;
};

dann noch mehrere Funktionen ermöglicht:

struct foo;
typedef const char *const (*IdMethod) (struct foo *this);
typedef int (*HandleMethod) (struct foo *this, list_t* args);
typedef int (*OtherMethod) (struct foo *this, int);
struct foo {
   IdMethod     idMethod;
   HandleMethod handleMethod;
   OtherMethod  otherMethod;
   char* key;
   unsigned long data;
};

Die Funktionen kann man dann noch in einem Struct zusammenfassen, die
man dann als "ein Zeiger" speichert. Dann kann man das generisch machen:
der erste Zeiger sind Funktionen, die erste davon immer IdMethod oder
was in der Art. Zum Speicher-Freigeben machen wir mal gleich noch ne
Löschfunktion auf Platz eins, kommt immer gut.
Dabei dann die "handle" nicht indiviuell an eine Instanz (Variable von
struct foo) sondern an einen Typen bindet:

struct foo;
typedef void (*DeleteMethod) (struct foo *this);
typedef const char *const (*IdMethod) (struct foo *this);
typedef int (*HandleMethod) (struct foo *this, list_t* args);
typedef int (*OtherMethod) (struct foo *this, int);
struct FooVirtualMethodTable {
   DeleteMethod delete;
   IdMethod     idMethod;
   HandleMethod handleMethod;
   OtherMethod  otherMethod;
};
struct FooVirtualMethodTable fooVirtualMethodTable {
   myDeleteFunction, /* <--- normale C Funktion */
   myIdFunction,
   myHandleFunction,
   myOtherMethod
};
#define class struct /* :-) */
class foo {
   FooVirtualMethodTable *vmt;
   char* key;
   unsigned long data;
};

Bei der Konstruktion trägt man in vmt (immer) ein Zeiger auf
fooVirtualMethodTable ein. Praktisch: wenn man Spezialbehandlung
braucht, kann man einen anderen Zeiger nehmen, klar. Die anderen Zeiger
können auch mit erweiterten Structs arbeiten, klar.

Weil das natürlich total redundant ist, kann man einen Compiler nehmen,
der das bissel erleichtert, dann schreibt man einfach:

class foo {

  public:
    void
    ~foo(void);

    const char *const 
    idMethod(void);
  
    int
    handleMethod(list_t* args);

  // usw.
};

(this wird implizit übergeben)

Na ja, und dann stellt man fest, dass Dein C-Code ja eigentlich das
gleiche ist, was Java macht, nur das Java "Luxus-Syntax" hat (und schon
fast ein halbes C++ ist!) 

:-)

> Vorurteilsfrei: das einzige, was ich in Java in dieser Richtung gesehen 
> habe sind lange Orgien, wo mittels TokenizerIrgendwas ein String in 
> String[] aufgespalten wurde und dann sowas wie:
> 
> if (t.getFirst.equals ("foo"))
>  // behandle keyword "foo"
> else if (t.getFirst.equals ("bar"))
>  // behandle keyword "bar"
> ...

ja, da gibts Pattern für, wie oben ist das für mehr als drei Strings
natürlich hässlich. In Perl geht das sehr schön:

my %handle {
  'foo' => { return "fooHandler\n"; },
  'bar' => { return lc("bar") . "Handler\n"; },  #really code!
  # ...
};

dann schreibt man:

$t = split(\s+, $line);  # \s+: beliebige whitespaces
print &handle->{$t};     # Einfach direkt im Hash aufrufen

:-)

> void* hash_new (int size);
> ...
> void hash_map (void*, int (*mapfunc) (const char*, void*, unsigned long), 
> unsigned long);

"riecht" ja schon nach OO... bloss die Func muss in das struct :)

> Es muss außer der Implementierung niemand wissen, wie ich die Tabelle
> organisiere bzw. ob es überhaupt eine Tabelle ist, ein Baum, was auch
> immer. Der Aufrufer bekommt bei der map-Funktion 

genau, eben eine Schnittstelle, nicht mehr.

> Auch wenn da schrecklich viele Klammern, Sternchen und & drin
> vorkommen werden, so habe ich als derjenige, der nur das schlanke
> Interface kennt, quasi alle Möglichkeiten über den Callback.

Man sollte für Funktionspointer am besten immer typedefs nehmen, liest
sich besser, finde ich, machen auch die meisten so, soweit ich weiss.

> Ich tendiere eher dazu, die generischsten EierLegendenWollmichSauen
> haben zu wollen, quasi supereinfachübersichtlich und trotzdem sehr
> mächtig. 

dumme, einfach Module verwendet man öfter wieder. Ich hab lieber viele
kleine dumme Bausteine. Wie oben die hash_map. Macht hashmap und nicht
mehr. Kann man immer mal gebrauchen. Kann man ja beerben (in C natürlich
bissel fummelig durch diese castereien).

> Ich will damit nicht sagen, dass ich Java per se nicht mag und C ja 
> sowieso ganz toll ist, die Frage nach dem Wie war ernst gemeint.

Na ja, hat alles so seine Vor- und Nachteile...

oki,

Steffen

-- 
Dieses Schreiben wurde maschinell erstellt,
es trägt daher weder Unterschrift noch Siegel.



Mehr Informationen über die Mailingliste linux-l