mikrocontroller.net

Forum: Compiler & IDEs Zeiger auf eine Methode einer Klasse -> Funktionszeiger


Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

wie castet mann einen Zeiger auf eine Methode einer Klasse in einen 
Zeiger auf eine Funkion? Ich hab z.b. folgendes:

struct C
{
   void mach()
   {
      cout<<"Hallo!"<<endl;
   }
};

typedef void (*pf_i) (int);

int main()
{
   C c;
   pf_i f;
   f=(pf_i)(&c.mach);
   f((int)(&c));
}

Ich hab alles mögliche durchprobiert(auch reinterpret/dynamic_cast), 
aber da kommt immer der Fehler, dass der cast nicht erlaubt ist. Wie 
kann ich den wegkriegen?

MfG Mark

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Garnicht. Das ist verboten, denn eine Methode braucht einen 
this-Pointer, den eine normale Funktion nicht bietet.

Ausnahme: Es handelt sich um einen statische Methode - die braucht kein 
this-Objekt.

Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo uhu,

deshalb ist f ja auch eine Funktion mit einem int als Argument. Beim 
Aufruf von f gebe ich den this-Zeger von c an :  f((int)(&c));

MfG Mark

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mark Prediger wrote:
> Beim Aufruf von f gebe ich den this-Zeger von c an :  f((int)(&c));

Nein, das tust du nicht. Tätest du es, sähe dein Aufruf so aus:

   c.mach();

Übrigens: Diese wilde casterei ist nicht gerade C++ - mäßig. Das sollte 
man nicht tun, wenn es nicht einen sehr guten Grund dazu gibt - den sehe 
ich hier nicht.

Autor: Hermann-Josef (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

wenn ich es recht verstehe, dann brauchst Du etwas was sich 
Pointer-to-member-function nennt, siehe auch:

http://www.cs.wustl.edu/~schmidt/PDF/C++-ptmf4.pdf

Ich habe das selbst erst einmal verwendet, es bekam damals eine mir 
nicht ganz verständliche und etwas martialische Warnung des Compilers 
(gcc 2.95) - funktioniert hat es aber. Mit gcc 3.3 sieht diese weniger 
schlimm aus, sollte dem mal nachgehen...

Das Konstrukt muss dann so aufgerufen werden, dass es an ein Objekt 
'angebunden' ist. Statische Methoden hingegen brauchen das nicht, also 
kann man beispielsweise die Adressen solcher Signal- oder ExitHandlern 
normal an die C-Schnittstelle übergeben.

HJ

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Ich hab alles mögliche durchprobiert(auch reinterpret/dynamic_cast),
> aber da kommt immer der Fehler, dass der cast nicht erlaubt ist. Wie
> kann ich den wegkriegen?

Die Fehlermeldung kannst du wegkriegen, aber funktionieren wird's dann 
trotzdem nicht.

> deshalb ist f ja auch eine Funktion mit einem int als Argument. Beim
> Aufruf von f gebe ich den this-Zeger von c an :  f((int)(&c));

Warum willst du eine Adresse in einem int speichern? Woher nimmst du die 
Annahme, daß ein int einen Zeigerwert überhaupt speichern kann? Auf 
vielen 64-Bit-Compilern z.B. geht das in die Hose, weil int nur halb so 
groß ist wie ein Zeiger. Abgesehen davon machst du hier Annahmen über 
den internen Aufbau der C++-Implementation.

Wie schon erwähnt, kann man einen Zeiger auf eine Memberfunktion 
verwenden:
#include <iostream>
using std::cout;
using std::endl;

struct C
{
   void mach()
   {
      cout<<"Hallo!"<<endl;
   }
};

typedef void (C::*pf_i) ();

int main()
{
   C c;
   pf_i f;
   f=&C::mach;
   (c.*f)();
}

Wozu brauchst du das eigentlich? Zeiger auf Memberfunktionen werden 
eigentlich meiner Erfahrung nach relativ selten gebraucht.

Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo, danke für die Antworten.

Da jeder die Frage gestellt hat, wofür ich es brauche, werde ich es mal 
kurz erläutern.

Ich habe eine Funktion SetTimer, die einen neuen Softwaretimer erstellt. 
Diese hat unter anderem als Parameter einen Zeiger auf eine Funktion, 
die ausgeführt werden soll, wenn die eingestellte Zeit vorbei ist und 
zwei integer, die der Funktion später als Parameter übergeben werden. 
Das klappt wunderbar mit normalen Funktionen, sobald es aber auch 
NICHTSTATISCHE Memberfunktionen sind, muss ich immer eine 
Zwischenfunktion einbauen, die einen der beiden ints zu einem Zeiger auf 
ein Objekt castet und dann die Memberfunktion über das Objekt aufruft.

Das ist mir auf Dauer aber zu umsändlich und verbraucht zu viele 
Ressourcen. Deshalb möchte ich die Memberfunktion direkt aufrufen. Dass 
das klappt(zumindest mit dem arm-elf-gcc) weiss ich, da ich eine 
Möglichkeit gefunden habe, die Memberfunktion direkt aufzurufen, in dem 
man ihren Namen in der LSS-Datei nachschaut und dann einen Prototypen 
davon mit 'extern "C"' davor deklariert.

Das einzige Problem ist halt nur, dass ich nicht weiss, wie man einen 
Memberfunktionszeiger zu einem Funktionszeiger direkt casten kann.

MfG Mark

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mark Prediger wrote:
>
> Das einzige Problem ist halt nur, dass ich nicht weiss, wie man einen
> Memberfunktionszeiger zu einem Funktionszeiger direkt casten kann.

Gar nicht.

Aber du willst unter Umständen mal das Web nach dem
Begriff 'Funktor' absuchen. Damit kann man das machen.
Allerdings: den Resourcenverbruach kriegst du damit auch
nicht runter. Der Pointer + ein Pointer zum Objekt muss
irgendwo gespeichert werden um den Aufruf zu machen.

Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich weiss was ein Funktor ist, aber nicht, wie er helfen könnte. Ein 
Funktor ist doch nichts weiteres als eine Klasse, bei der der Operator 
() überladen wurde. Aber wie soll ich dann normale Funktionen benutzen 
können?

MfG Mark

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sieh mal nach, ob die Methode, die du casten willst, überhaupt auf 
irgendwelche Membervariablen zugreift.

Wenn sie das nicht tut, dann kannst du sie als static vereinbaren. 
Statische Memberfunktionen kann man einem Funktionspointer mit passender 
Signatur zuweisen.

struct C
{
   static void mach()
   {
      cout<<"Hallo!"<<endl;
   }
};

Allerdings bringt das auch keinen Performance-Gewinn gegenüber einem 
Aufruf C::mach().

Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo uhu,

es ist ja genau das Ziel, NICHTSTATISCHE Memberfunktionen verwenden zu 
können.

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Na ja, dann mußt du eben die Kosten dafür in Kauf nehmen... 
Ladendiebstahl beim Compiler gibts nicht.

Autor: Hugo Mildenberger (hmld)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Der Weg, sich unter Umgehung der VMT den "mangled-name" aus der 
Symboltabelle herauszusuchen und den int - Parameter in einen Zeiger auf 
eine Instanz zu casten, funktioniert genau für Umgebung in der 
sizeof(int)==sizeof(void *) gilt, und genau für Instanzen derjenigen 
Klasse, zu der die Methode gehört. Eine von einer abgeleiteten Klasse 
überschriebene  Funktion kann so natürlich nicht aufgerufen werden, und 
damit ist der Mehrwert vollständig negativ.

Man kann diese unglückliche Idee automatisieren -- via dlopen und bfd 
Symboltabelle lesen, Adresse der Methode einer Klasse bei Kenntnis des 
Mangling-Schemas des Compilers herausfischen (zB. via regex und der 
(undokumentierten) libiberty - Funktion 'cplus_demangle_mangled_name', 
sich nebenbei mit 'weak' und 'debug'-Symbolen und außerdem den so gut 
wie garnicht dokumentierten elf-Trampolins herumschlagen), diese dann 
endlich an einen C"-function-pointer zuweisen und den o.g. int-Parameter 
anstelle des sonst impliziten 'this' - Pointers übergeben.

Aber was für ein Aufwand für unportablen Code, der genau bis zur 
nächsten Compiler- & Linkerversion durchhält. Der Aufwand für eine 
saubere Lösung via zwischengeschalteter "C" - Funktion und nachfolgendem 
Aufruf von this->method() über die VMT ist ziemlich konstant, und wenn 
dafür die Prozessorleistung nicht reicht, dann  ist der eben gerade ein 
wenig zu langsam für einen objektorientierten Ansatz.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mark Prediger wrote:
> Ich weiss was ein Funktor ist, aber nicht, wie er helfen könnte. Ein
> Funktor ist doch nichts weiteres als eine Klasse, bei der der Operator
> () überladen wurde. Aber wie soll ich dann normale Funktionen benutzen
> können?
>
> MfG Mark

Ungefähr so. (hab auf meinem alten Laptop keinen C++ Compiler)

Zunächst mal eine Basisklasse
class FunktorBase
{
  public:
    virtual ~FunktorBase() {}

    virtual void operator() ();
};

Von der leitest du einen Funktor ab, der mit Objekten
umgehen kann. Diesmal als Template implementiert.
template class <T>  ObjFunktor : public FunktorBase
{
  public:
    ObjFunktor( T pObj, void (T::*pFunc)() )
    : m_pObj( pObj ), m_pFunc( pFunc )
    {}

    virtual void operator() ()
    {
      (*m_pObj).(*m_pFunc)();
    }

  protected:
    T * m_pObj;
    void (T::*m_pFunc)();
}

Dann einen Funktor, der eine normale Funktion kapselt
typedef void (*CFunc)();

class CFuncFunktor : public FunktorBase
{
  public:
    CFuncBase( CFunc pFunc )
    : m_pFunc( pFunc )
    {}

    virtual void operator() ()
    {
      (*m_pFunc)();
    }

  protected:
    CFunc m_pFunc;
}

Damit hast du 2 Klassen, die jeweils gleiche Aufrufsyntax
für den Operator() haben und über die in ihnen gekapselten
Daten den indirekten Funktionsaufruf machen. Da beide Klassen
von derselben Basisklasse abgeleitet sind, können sie polymorph
eingesetzt werden
class A
{
  public foo();
};    

void cfoo()
{
}

FunktorBase* pFunctionToCall;

int main()
{
  A objA;

  ObjFunktor   FunctionInA( objA, objA.foo );
  CFuncFunktor FreeFunction( cfoo );

  // und jetzt kommts:

  // das ruft jetzt die Funktion A::foo vom Objekt A auf
  pFunctionToCall = &ObjFunctor;
  (*pFunctionToCall)();

  // waehrend dieser Aufruf die freie Funktion foo aufruft
  pFunctionToCall = &FreeFunction;
  (*pFunctionToCall)();
}

Wie gesagt: Ich konnte da jetzt nichts testen. Da sind jetzt sicher
ein paar Syntaxfehler drinnen und mit der Syntax der Funktionspointer
auf Memberfunktionen stimmt sicher auch noch nicht 100%. Aber das
Prinzip sollte sichtbar sein.

Denn genau das ist das Prinzip eines Funktors: Du versteckst die
Details wie der Aufruf konkret zu machen ist in einer eigenen
Klasse. Von ausserhalb rufst du einfach nur den operator() des
Funktors auf und der Funktor selbst weiss, wie der konkrete
Aufruf weitergeleitet werden muss. Und da du das ganze polymorph
über virtuelle Operatoren (Funktionen) abwickeln kannst, ist
der Aufruf von ausserhalb immer gleich.

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oh, Karl Heinz war mal wieder schneller.

Dafür ist mein Code getestet ;-)

Scheint aber ziemlich genau das Gleiche zu sein, nur dass Karl Heinz
noch eine zusätzliche Klasse für freie Funktionen mit dabei hat.

Wenn ich dich richtig verstanden habe, suchst du einen Weg, eine
Methode zusammen mit einem Objekt einer beliebigen Klasse in eine
Variable zu packen, so dass über diese Variable <Objekt>.<Methode>()
aufgerufen werden kann.

Folgende Lösung benutzt keinerlei Casts und ist damit typsicher. Der
Aufrufer muss nur die Klasse BoundMethodBase, nicht aber die Klasse
des Objekts, dessen Methode aufgerufen werden soll, kennen.

Es wird davon ausgegangen, dass die aufzurufende Methode vom Typ void
ist und keine Argumente entgegen nimmt. Natürlich kann der Code durch
Änderung der Methodenpointertypen auch an andere Methodentypen
angepasst werden.
#include <iostream>

// Klassenunabhängige Basisklasse für die Verwendung beim Aufruf
class BoundMethodBase {
  public:
    virtual void operator()() = 0;
};

// Klassenspezifische abgeleitete Klasse für die Verwendung bei der
// Instantiierung
template <class C>
class BoundMethod: public BoundMethodBase {
  public:
    BoundMethod(C &object, void (C::*method)()):
      m_object(object), m_method(method) {
    }
  void operator()() {
    (m_object.*m_method)();
  }
  private:
    C &m_object;
    void (C::*m_method)();
};

// Anwendungsbeispiel: Eine an die Instanz 'test' der Klasse 'Test'
// gebundene Methode 'show' wird als Objekt 'bm' angelegt, dieses an
// die Funktion 'function' übergeben und dort aufgerufen.
class Test {
  public:
    Test(int val) {
      m_val = val;
    }
    void show() {
      std::cout << m_val << std::endl;
    }
  private:
    int m_val;
};

void function(BoundMethodBase &bm) {
  bm();
}

int main() {
  // Instanz der Klasse Test
  Test test(123);

  // Erzeugen der gebundenen Methode 'test.show'
  BoundMethod<Test> bm(test, &Test::show);

  // Aufruf
  function(bm);
  return 0;
}

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zurück zum OP:
Mark Prediger sucht einen Weg, beim Aufruf einer Memberfunktion einige 
Bytes einzusparen.

Er will dazu den this-Pointer in ein int casten und als normalem 
Parameter übergeben.

Dabei übersieht er, daß ein Aufruf einer Memberfunktion o->member() aus 
Sicht des Compilers nichts anderes ist, als ein Aufruf einer Funktion 
mit einem Pointerparameter; die Schreibweise o-> ist also genaugenommen 
nur syntaktischer Zucker...

Marks Überlegungen, C++ zu überlisten, führen in eine Sackgasse, denn es 
gilt: Beitrag "Re: Zeiger auf eine Methode einer Klasse -> Funktionszeiger"

Zur Lösung des Problems gibt es zwei Möglichkeiten:

1. Die Regeln von C++ akzeptieren und ohne Vergewaltigungen anwenden
2. Die Sache in C programmieren, die Objektstruktur nachbilden und
   sich eine Konvention basteln, nach der die Objektadresse an die
   Methoden übergeben wird.

Diese Sachen mit Funktoren, die hier beschrieben sind, sind zwar ansich 
interessant, führen aber im Kontext dieses Threads noch weiter ins 
Abseits

Grotesk...

Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Karl Heinz &  yalu:

das ist eine sehr interessante Idee, die auch sicher funktionieren 
würde.

Doch wie Uhu bereits gesagt hat, möchte ich Speicherplatz und Laufzeiz 
sparen, bei Eurer Lösung wäre aber genau das Gegenteil der Fall, da man 
um die  dynamische Speicherallokierung nicht herumkommen würde.

Uhu wrote:
>Dabei übersieht er, daß ein Aufruf einer Memberfunktion o->member() aus
>Sicht des Compilers nichts anderes ist, als ein Aufruf einer Funktion
>mit einem Pointerparameter; die Schreibweise o-> ist also genaugenommen
>nur syntaktischer Zucker...

Wieso 'übersieht'??? Das habe ich doch schon von Anfang an behauptet. 
Genau deswegen wollte ich ja den Objektzeiger in einem int speichern und 
diesen dann als Parameter übergeben, da der Compiler intern das gleiche 
macht. Dass dann der Code nicht portabel ist, ist mir klar.

zu 2.: Das habe ich schon befürchtet. Weil es mir aber zu umständlich 
ist, werde ich wohl bei den doppelten Methoden bleiben müssen.

MfG Mark

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mark Prediger wrote:
> Wieso 'übersieht'??? Das habe ich doch schon von Anfang an behauptet.
> Genau deswegen wollte ich ja den Objektzeiger in einem int speichern und
> diesen dann als Parameter übergeben, da der Compiler intern das gleiche
> macht. Dass dann der Code nicht portabel ist, ist mir klar.

Und was hättest du mit diesem Hack gewonnen? Garnichts, außer einem 
Programm, das die Katastrophe selbst ist...

Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Damit hätte ich keine doppelten Methoden und eine ein paar Takte 
geringere Laufzeit, das ist alles.

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Damit hätte ich keine doppelten Methoden und eine ein paar Takte
> geringere Laufzeit, das ist alles.

Wenn in der aufgerufenen Funktion nur wenig mehr als nichts steht, ist
der Aufruf-Overhead vernachlässigbar. Hab's mal gestestet (Pentium4,
3,2 GHz):

Der Aufruf von (testptr->*methptr)() dauert ca. 4,2 ns. Er entspricht
dem, was du in deinem ersten Post machen wolltest: Eine Funktion wird
über einen Funktionspointer (methptr) mit einem (in diesem Fall
versteckten) Argument, nämlich testptr, aufgerufen.

Der Aufruf von bm() (wie in meinem obigen Post) dauert 8,2 ns, also 4
ns länger. Das fällt nur dann ins Gewicht, wenn deine Funktion eine
Laufzwit von nur wenigen ns hat.

Wenn du, um 4 ns einzusparen, trotzdem die wilde Casterei bevorzugst,
kannst du das natürlich tun, indem du sowohl den Objekt- als auch den
Methodenpointer in eine einheitliche Klasse (DummyClass) castest:
#include <iostream>

class Test {
  public:
    Test(int val) {
      m_val = val;
    }
    void show() {
      std::cout << m_val << std::endl;
    }
  private:
    int m_val;
};

class DummyClass;

void function(DummyClass *dummy, void (DummyClass::*method)()) {
  (dummy->*method)();
}

int main() {
  // Instanz der Klasse Test
  Test test(123);

  function((DummyClass *)&test, (void (DummyClass::*)())(&Test::show));
  return 0;
}

Dies dauert ebenfalls 4,2 ns, führt allerdings auf Grund des Casts von
&test bei einem Optimierungslevel ab -O2 zu folgender Meldung:
warning: type-punning to incomplete type might break strict-aliasing rules

Sie bedeutet nichts anderes, als dass dein Programm auf Grund von
Optimierungen möglicherweise nicht mehr funktionieren wird. Um sicher
zu gehen, musst du entweder mit -O1 oder mit -O2 -fno-strict-aliasing
kompilieren. Dann verschwindet auch die Warnung.

Aber wer weiß, ob du dann die 4 ns nicht an anderer Stelle (auf Grund
schlechterer Optimierung) verlierst ;-)

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Noch ein Nachtrag:

> Doch wie Uhu bereits gesagt hat, möchte ich Speicherplatz und
> Laufzeiz sparen, bei Eurer Lösung wäre aber genau das Gegenteil der
> Fall, da man um die  dynamische Speicherallokierung nicht
> herumkommen würde.

Bei unserer Lösung wird nichts dynamisch allokiert.

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Kann es sein, daß so ein Konstrukt dem nahe kommt, was du meinst:

class base {
  protected:
   void methode() {
      ...
   }

   // Gemeinsame Daten
};

class abgeleitet1 : public base {
   // Daten, die nur in abgeleitet1 benötigt werden
};

class abgeleitet2 : public base {
   // Daten, die nur in abgeleitet2 benötigt werden
};


abgeleitet1 o1;
abgeleitet2 o2;

...

o1.methode();
o2.methode();

Da methode in der öffentlichen Basisklasse definiert ist, kann sie 
sowohl von abgeleitet1 als auch abgeleitet2 aufgerufen werden. Der Code 
existiert nur einmal.

Falls das die Lösung ist, dann solltest du vor allem deine Kenntnisse in 
Deutsch deutlich ausbauen...

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Da methode in der öffentlichen Basisklasse definiert ist, kann sie
> sowohl von abgeleitet1 als auch abgeleitet2 aufgerufen werden.

Das ist schon richtig, nur dass die Methode für beide abgeleiteten
Klassen die gleiche ist und nicht auf deren jeweils spezifische Daten
zugreifen kann. Dann kann man sich die abgeleiteten Klassen sparen.

Oder man macht die Methode virtuell und implementiert sie in den
abgeleiteten Klassen unterschiedlich. Wenn man dann noch die Klassen
base, abgeleitet1 und abgeleitet2 von der Klasse der eigentlichen
Anwendung trennt, um diese nicht von base ableiten zu müssen und
abgeleitet1 und abgeleitet2 zu einer per Templateparameter adaptier-
baren Klasse zusammenfasst, hast du exakt die weiter oben von Karl
Heinz und mir vorgeschlagene Lösung :)

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
yalu, du scheinst ein Fan von Monsterlösungen zu sein, die Marks Ziel, 
Speicherplatz und Ausführungszeit einzusparen grandios verfehlen.

Mark hat sich leider sehr nebulös ausgedrückt, was er eigentlich machen 
will. Jetzt muß er entscheiden, ob er mit einer Basisklasse was anfangen 
kann - nicht du.

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Recht hast du, also warten wir mal ab ...

Als Monster würde ich die Lösung aber nicht bezeichnen, und wenn,
dann höchstens so ein ganz niedliches Schmunzelmonster :)

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Kompromiß: Einigen wir uns auf Munzelschmonster.

Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
OK, weil hier anscheinend keiner vestanden hat, was ich  brauche, fange 
ich jetzt nochmal ganz von vorne an.

Ich habe eine Funktion SetTimer(), die einen Funktionszeiger, die Zeit 
zwischen den Aufrufen der Funktion sowie 2 integer, die später an die 
Funktion übergeben werden, als Parameter hat. Bsp:
#include <iostream>

using namespace std;

void timer_test(int param1,int param2)
{
  cout<<"Parameter 1 = "<<param1<<", Parameter 2 = "<<param2<<endl;
}

int main()
{
  SetTimer(1000,timer_test,123,456);
  /*
  Konfiguriert einen Software-Timer, der die Funktion 'timer_test' alle 1000 ms
  aufruft und ihr dabei die integer 123 und 456 übergibt. Jede Sekunde wird also 
  "Parameter 1 = 123, Parameter 2 = 456\n" auf dem Bildschirm ausgegeben
  */
  while(1);
}

Nun möchte ich aber auch Zeiger auf nichtstatische Methoden an die 
SetTimer-Funktion übergeben können. Der erste int soll dabei der 
this-Zeiger sein. Bisher hebe ich es immer so gemacht:
#include <iostream>

using namespace std;

class MyClass
{
  int integer;

  public:

  MyClass(int i):integer(i) {};//Kunstruktor

  void Ausgabe(int i)
  {
    cout<<"Der integer des Objekts = "<<integer<<" , der übergebene int = "<<i<<endl;
  }
  
  static void _Ausgabe(int This,int i)
  {
    reinterpret_cast<MyClass*>(This)->Ausgabe(i);
    //interpretiert den ersten int als this-Zeiger und ruft über ihn die Methode
    //Ausgabe() auf
  }
};

int main()
{
  MyClass mc=123; //Objekt der Klasse MyClass mit integer=123
  SetTimer(1000,_Ausgabe,reinterpret_cast<int>&mc,456);
  /*
  Jetzt wird die statische Methode _Ausgabe jede Sekunde aufgerufen. Der erste 
  Parameter ist der Zeiger auf das Objekt, mit dem gearbeitet werden soll. Auf dem
  Bildschirm wird "Der integer des Objekts = 123, der übergebene int = 456\n" 
  ausgegeben
  */
  while(1);
}
Die Methode 'Ausgabe' musste ich zweimal implementieren, einmal als 
nichtstatische und einmal als statische. Doch wie bereits erwähnt, ist 
der Aufruf einer Memberfunktion nichts weiteres als der Aufruf einer 
normalen Funktion mit dem Objektzeiger. Genau deshalb will ich 
_Ausgabe() einsparen und dem Timer die Adresse von Ausgabe() übergeben, 
um die Funktion direkt ohne Zwischenstelle aufrufen zu können. Bei dem 
Versuch, einer Zeiger auf eine Methode in einen Funktionszeiger, so wie 
ihn SetTimer() verlangt, zu casten, kommt jedoch immer der Fehler, dass 
man es nicht darf.

Ich konnte es aber nicht lassen, so habe ich den Namen der Methode in 
der .lst-Datei nachgeschaut und damit einen Prototypen mit extern "C" 
davor deklariert. Jetzt denkt der Compiler, '_ZN7MyClass7AusgabeEi' wäre 
eine normale Funktion mit 2 integern als Parameter. Die Adresse lässt 
sich problemlos an SetTimer() übergeben und der Aufruf funktioniert wie 
gewollt (auch wenn es manche von Euch nicht glauben wollen). Trotzdem 
würde ich es gerne auf eine C/C++-Art lösen.

>Wenn in der aufgerufenen Funktion nur wenig mehr als nichts steht, ist
>der Aufruf-Overhead vernachlässigbar. Hab's mal gestestet (Pentium4,
>3,2 GHz):
Der Code sollte eigentlich auf einem LPC2138 mit 59MHz und 32kB RAM 
laufen...

MfG Mark

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> OK, weil hier anscheinend keiner vestanden hat ...

Ich glaube ich habe im Wesentlichen schon verstanden, was du willst.
Das einzige, was mich gefragt habe, ist, warum du mit aller Gewalt den
Pointer auf das Objekt in ein Integer casten willst.

Das ist mir jetzt (hoffentlich) klar geworden:

Du willst an SetTimer eine Funktion und zwei Argumente, mit denen die
Funktion später aufgerufen wird, übergeben. Da die Funktionsargumente
je nach Funktion ganz unterschiedlicher Art sein können, hast du dich
für int als denjenigen Datentyp entschieden, der in den meisten Fällen
passt.

Jetzt willst du aber nicht nur gewöhnliche Funktionen, sondern
nichtstatische Methoden eines Objekts an SetTimer übergeben. Da
Methodenpointer anscheinend nicht in Funktionspointer gecastet werden
können, hast du die statische Wrappermethode _Ausgabe geschrieben. Der
für den Aufruf der Methode erforderliche Pointer auf das Objekt kann
natürlich nur über eines der beiden Integer-Argumente übergeben
werden, die aber leider vom falschen Datentyp, nämlich int, sind. Also
bleibt nichts anderes übrig, als den Pointer in ein int zu casten.

Richtig bis hierher?

Es liegt nahe, dass die zusätzlich eingeführte statische
Wrappermethode

1. zusätzliche Schreibarbeit

2. Laufzeit-Overhead

3. Speicher-Overhead

bedeutet. Ganz so schlimm ist es aber nicht:

Zu 1: Der Schreibaufwand ist da, keine Frage, aber du könntest ihn
mittels eines Makros etwas reduzieren,da die Funktionen ja für alle
Klassen und Methoden sehr ähnlich aussehen.

Zu 2: Der Laufzeit-Overhead kann vermieden werden, wenn die Methode
Ausgabe in die statische Methode _Ausgabe geinlinet wird. So wie du
das Beispielprogramm gepostet hast, passiert das automatisch. Der vom
Compiler generierte Code von _Ausgabe ist dann exakt der gleiche wie
der von Ausgabe (hab's im Assemblercode nachgeschaut, allerdings für
den x86-GCC, einen ARM-GCC habe ich gerade nicht da). D. h.
letztendlich wird nur eine Funktion aufgerufen, die genau so
aussieht wie die aufzurufende Methode.

Zu 3: Wenn du die Methode Aufruf sonst nirgends in deinem Programm
verwendest, entsteht auch kein Speicher-Overhead. Wenn doch, wird
natürlich für jeden Aufruf der Code kopiert. Dafür hast du dann einen
weiteren Geschwindigkeitsvorteil, da bei den anderen Aufrufen
überhaupt kein Aufruf-Overhead mehr entsteht.

>> ... Hab's mal gestestet (Pentium4, >3,2 GHz):
> Der Code sollte eigentlich auf einem LPC2138 mit 59MHz und 32kB RAM
> laufen...

Es war mir schon klar, dass du mit hoher Wahrscheinlichkeit ein
anderes Zielsystem hat. Da der LPC natürlich deutlich langsamer als
ein Pentium ist, ist auch der absolute Overhead entsprechend größer.
Da aber auch der Code der Methode entsprechend langsamer ausgeführt
wird, ist der prozentuale Overhead wieder etwa der gleiche. Ich wollte
mit den Zahlen nur ausdrücken, dass es wenig Sinn hat, in einem
Programm zweifelhafte Konstrukte einzusetzen, nur um einen
Geschwindigkeitszuwachs von vielleicht 1% zu erzielen.

Kurz und klein:

Die Lösung mit der Wrappermethode ist schnell, aber auf Grund der
Casterei nicht auf beliebige Architekturen portierbar. Vielleicht muss
sie das auch gar nicht sein. Wenn Geschwindigkeit alles ist, würde ich
bei dieser Lösung bleiben. Vielleicht kannst du ja das erste der
beiden Funktionsargumente von int nach void* ändern, was besser zu dem
tatsächlich übergebenen Datentyp (Pointer) passt und möglicherweise
auch die Gefahr von Aliasing-Problemen senkt.

Die Lösung, bei der du den internen Symbolnamen aus der LSS-Datei
verwendest, ist m. E. genau gleich schnell, das Programm wird aber
möglicherweise schon mit der nächsten GCC-Version nicht mehr ohne
Änderungen kompilierbar sein. So würde ich's auf keinen Fall machen.

Die sauberste, portabelste und C++igste Lösung ist m. E. immer doch
die von Karl Heinz und mir weiter oben vorgschlagene, auch wenn sie
etwas langsamer ist und eine kleine Änderung des Interfaces von
SetTimer erfordert. Aber ich möchte dir nichts aufschwatzen, letzt-
endlich musst du mit dem Ergebnis glücklich sein.

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Mark
Na diese Beschreibung unterscheidet sich deutlich von dem Zeugs im 
ersten Posting, warum nicht gleich...

Ganz offensichtlich mußt du Ausgabe nicht zweimal implementieren, denn 
_Ausgabe erfüllt einen ganz anderen Zweck.

Der englische Fachausdruck dafür ist thunk - frag mich nicht, wie man 
sowas auf Deutsch nennt... http://en.wikipedia.org/wiki/Thunk

_Ausgabe braucht auch keine Memberfunktion zu sein, weil sie nur eine 
Kompatibilitätsschicht darstellt, um eine Methode eines bestimmten 
Objektes der Klasse MyClass zu adressieren.

Thunks fand man z.B. bei Windows in Massen dort, wo alter 16-Bit- mit 
32-Bit-Code zusammenarbeitete. Das sind allerdings oft ziemlich krude 
Hacks aus Macros und Hex-Strings, die direkt ausgeführt werden.

Wenn du über SetTimer eine Methode aus MyClass aufrufen willst, dann 
mußt du das so machen, wie in deinem letzten Posting, oder wie von den 
Kollegen oben vorgeschlagen. Wegen 3 Byte einen mords Aufstand zu machen 
und sich deshalb auch noch einen Sack potentielle Folgefehler 
einzuhandeln, ist keine so dolle Idee...

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Das ist mir auf Dauer aber zu umsändlich

Wenn du es so oft brauchst, schreibe dir ein Template oder Makro dafür. 
Alternativ könntest du auch die ganze Timersache hinter einer Klasse 
wegkapseln.

> und verbraucht zu viele Ressourcen.

Wie hast du das ermittelt?

> Jetzt denkt der Compiler, '_ZN7MyClass7AusgabeEi' wäre eine normale
> Funktion mit 2 integern als Parameter. Die Adresse lässt sich
> problemlos an SetTimer() übergeben und der Aufruf funktioniert wie
> gewollt (auch wenn es manche von Euch nicht glauben wollen).

Ich glaube das schon. Es ist halt extrem unportabel. Daß du hier die 
Adresse durch einen int schleifst, ist dabei gar nicht das schlimme. 
Meistens geht das ja noch gut. Aber der Funktionsname kann bei einer 
älteren oder neueren Version desselben Compilers für dieselbe 
Zielplattform auch schon mal unterschiedlich sein.
Ach ja: Wieviel Laufzeitersparnis hat das nun effektiv gebracht?

> Trotzdem würde ich es gerne auf eine C/C++-Art lösen.

Das hattest du ja schon, aber willst du nicht.

Autor: Hugo Mildenberger (hmld)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>OK, weil hier anscheinend keiner vestanden hat, was ich  brauche, fange
>ich jetzt nochmal ganz von vorne an.

Das ist sehr unüberlegt oder sehr überheblich. Alle in diesem Thread 
haben verstanden, und besonders ein Uhu mit bekannt scharfem Blick. 
Manche dachten, daß da einer vielleicht die Sprache nicht so gut kennt 
und haben entsprechende Lösungen vorexerziert. Andere haben einfach nur 
davon abgeraten, wegen ein paar Taktzyklen Code mit Minenfeld-Charakter 
zu schreiben.

Wahrscheinlich jeder der Beteiligten hat sich schon über den jeweiligen 
Entwickler von schlecht entworfenen C-Schnittstellen wie die des 
'SetTimer' geärgert (wo es doch ein Leichtes gewesen wäre, wenigstens 
einen void* Parameter vorzusehen anstatt zwei ints ... manchmal war man 
es ja auch selbst.)

Wer sich hier an Unvollkommenheit stört, dem bleibt nicht viel, als 
solchen Kram herauszuwerfen, und schon auf ISR-Ebene eine 
Schnittstellenklasse anzusetzen. Je nach Umfeld kann das sogar 
Laufzeitvorteile bringen, weil man dadurch in relativ übersichtlicher 
Weise zu einem 'tickless'-System kommt, bei dem ein Hardware-Timer auf 
dasjenige delta-T gesetzt wird, zu dem der nächste Software-Timer auch 
wirklich ablaufen soll.

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hugo Mildenberger wrote:
> Wahrscheinlich jeder der Beteiligten hat sich schon über den jeweiligen
> Entwickler von schlecht entworfenen C-Schnittstellen wie die des
> 'SetTimer' geärgert (wo es doch ein Leichtes gewesen wäre, wenigstens
> einen void* Parameter vorzusehen anstatt zwei ints ... manchmal war man
> es ja auch selbst.)

Also da muß ich ausnahmsweise mal die vermeintlichen Murkser in Schutz 
nehmen: Schnittstellen-Muster wie das von SetTimer sind schon uralt - 
älter als der Datentyp void *, den es in frühen Versionen von C noch 
nicht gab. Damals war es üblich, statt void *, int zu verwenden.

Das ist eben die Last des Erbes aus früheren Zeiten. Allerdings kann man 
um solche Schnittstellen was komfortableres bauen - heute sind die paar 
Byte, die man dafür braucht, um Zehnerpotenzen billiger, als seinerzeit. 
Auf Routinen aus dieser Zeit muß man deshalb nicht verzichten, die sind 
nämlich hochgradig bewährt...

Autor: Arc Net (arc)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Relativ portable Lösung mit ziemlich viel Hintergrundinfos und 
berechtigter Kritik an den C++-Methodenzeigern

http://www.codeproject.com/cpp/FastDelegate.asp

Anderer "Hack"
struct FMFPtr {
    Base* Object;
    union {
        void (*FPointer)(int param);
        void (Base::*MFPointer)(int param);
    };
};

class Timer {
public:
    Timer() { };

    void AddTimer(Base* basePtr, void (Base::*ptt)(int param)) {
        timerFunc.Object = basePtr;
        timerFunc.MFPointer = ptt;
    }
    void AddTimer(void (*ptt)(int param)) {
        timerFunc.Object = NULL;
        timerFunc.FPointer = ptt;
    }

    void Trigger() {
        if (timerFunc.Object) {
            (timerFunc.Object->*timerFunc.MFPointer)(123);
        } else {
            timerFunc.FPointer(123);
        }
    }
protected:
    FMFPtr timerFunc;
};

Autor: Hugo Mildenberger (hmld)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Also da muß ich ausnahmsweise mal die vermeintlichen Murkser in Schutz
>nehmen: Schnittstellen-Muster wie das von SetTimer sind schon uralt -
>älter als der Datentyp void *, den es in frühen Versionen von C noch
>nicht gab. Damals war es üblich, statt void *, int zu verwenden.


Ah, das wußte ich noch nicht. Dagegen ist jetzt klar, daß dieser Uhu 
schon im Gebälk saß, als Robotron noch Schreibmaschinen produzierte.

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Uhus sitzen nicht im Gebälk, das sind keine Kulturfolger, wie die 
Schleiereule...

Autor: Mark .. (mork)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

>>OK, weil hier anscheinend keiner vestanden hat, was ich  brauche, fange
>>ich jetzt nochmal ganz von vorne an.

>Das ist sehr unüberlegt oder sehr überheblich. Alle in diesem Thread
>haben verstanden, und besonders ein Uhu mit bekannt scharfem Blick.

Ja das war es, dacher möchte ich mich dafür entschuldigen. Ich glaube 
auch, dass es alle verstanden haben und bin dankbar für die 
verschiedenen Vorschläge. Ich wollte halt einfach keine neue 
Gesamtlösung  für die Timerfunktion, sondern einfach nur wissen, wie man 
einen normalen Zeiger in einen Methodenzeiger castet. Mittlerweile denke 
ich aber, dass eine komplette Neuimplementierung doch am besten wäre.

MfG Mark

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.