Forum: Mikrocontroller und Digitale Elektronik C++ Funktionszeiger von static-Methode aus auf nicht-static-Methode einer Klasse


von Alexander I. (daedalus)


Lesenswert?

Hallo,

ich muss gerade ein Stückchen Code in KEIL für ARM7 importieren... in 
einem PowerPC-Compiler funktioniert das nachfolgende Konstrukt 
einwandfrei:
1
class IoPort : public List<IoPort>
2
{
3
public:
4
  static void InitAll(void) { ForEach(Init); } // Fehler!
5
  virtual void Init(void) = 0;
6
};

Beim KEIL erhalte ich den Fehler:
#504-D: nonstandard form for taking the address of a member function

Das kommt auch, wenn ich die InitAll "ausschreibe" mit
1
static void InitAll(void) { ForEach((void (IoPort::*)(void))Init); }

Erklärung der Funktion:
Im System sind lauter von IoPort abgeleitete Klassen instanziert, die 
z.B. einen UART oder andere Hardware abbilden. Alle IoPorts sind durch 
Ableitung von der Templateklasse List in einer verketteten Liste 
verknüpft, die auch die static-Funktion ForEach implementiert. Dadurch 
wird einmal in der main()-Schleife ein
1
IoPort::InitAll();
aufgerufen, was zur Folge hat, dass die verkettete Liste von IoPorts 
abgearbeitet wird und jede IoPort-Instanz einen "Init"-Aufruf erhält und 
z.B. Pins als Ein- oder Ausgang setzt.

- Wieso spuckt KEIL hier einen Fehler aus, der PowerPC-Compiler aber 
nicht?
- Was bedeutet der Fehler überhaupt?
- Wie sieht eine Alternative aus?

von Klaus W. (mfgkw)


Lesenswert?

Alexander I. schrieb:
> in
> einem PowerPC-Compiler funktioniert das nachfolgende Konstrukt
> einwandfrei:

> falsch: glaube ich nicht.
> falsch: Wie soll der Compiler dnen wissen, für welches Objekt jeder
> falsch: Init()-Aufruf gemacht werden soll?

Nachtrag:
Sorry, hatte überlesen, daß IoPort von List abgeleitet ist.
Da muß ich nochmal nachdenken.

Von welcher List eigentlich?

von klaus (Gast)


Lesenswert?

Klaus Wachtler schrieb:
> Wie soll der Compiler dnen wissen, für welches Objekt jeder
>
> Init()-Aufruf gemacht werden soll?

Habe ich auch erst gedacht. Die vermute die Idee ist, da die 
Basis-Klasse als Template definiert ist, die ForEach Funktion über alle 
enthaltenen Elemente e iterieren zu lassen und dann einfach "e.Init()" 
aufzurufen. Was ich nicht verstehe wie man den Parameter "Init" im 
Template dafür heranziehen kann.

von Karl H. (kbuchegg)


Lesenswert?

Wie sieht das List Template aus. Und wie dessen ForEach Member?

von Alexander I. (daedalus)


Lesenswert?

Durch Instanzierung von IoPort-Klassen wird jeweils automatisch ein 
neues Element in eine (globale) List aller IoPorts eingetragen.

mit
1
static void InitAll(void) {for(IoPort* p = First; p; p = p->Next){p->Init();}}

klappt es... da hab ich quasi die ForEach-Funktion neu geschrieben und 
so die Funktionspointerproblematik umgangen. Aber so an sich hätte mich 
der Sachverhalt schon interessiert...

von Alexander I. (daedalus)


Lesenswert?

1
template<class T>
2
class List
3
{
4
public:
5
  static T* First;
6
  static T* Last;
7
  T* Next;
8
public:
9
  List()
10
  {
11
    T* node = (T*)this;
12
    if(!First) First = node;          // Dies ist das erste Element
13
    if(Last) {Last->List::Next = node;}     // Verkette das letzte Element mit Diesem
14
    Last = node;                      // Dieses ist nun das letzte Element
15
    Next = 0;                         // Es gibt noch kein nächstes Element
16
  }
17
  static void ForEach(void (T::*func)(void))
18
  {
19
    for(T* node = First; node; node = node->Next)
20
    {
21
      (node->*func)();                // Rufe die zu iterierende Funktion auf
22
    }
23
  }
24
};

von Klaus W. (mfgkw)


Lesenswert?

Und: welches ForEach?
Sowohl dieses als auch die List sind nicht aus std::?

Abgesehen davon drohe ich gerade mental in eine Endlosschleife zu
kommen, weil IoPort abgeleitet wird von einer Liste (IoPort ist
also eine ganze Liste, nicht ein Port). Diese Liste enthält
wiederum lauter Elemente vom Typ IoPort, das jeweils eine
Liste von....

Ohne es probiert zu haben, weiß ich erstens nicht, ob du
das wirklich so willst, und zweitens ob je ein C++-Compiler
das wirklich übersetzen mag.

von Karl H. (kbuchegg)


Lesenswert?

klaus schrieb:

> aufzurufen. Was ich nicht verstehe wie man den Parameter "Init" im
> Template dafür heranziehen kann.

Ich denke da kommen wir der Sache dann schon näher.
Vermutung: ForEach übernimmt einen Funktionspointer.

Probier mal:

  static void InitAll(void) { ForEach(IoPort::Init); }

Eventuell
  static void InitAll(void) { ForEach(&IoPort::Init); }

obwohl das IMHO nicht nötig sein sollte.

von Klaus W. (mfgkw)


Lesenswert?

Mal so von Klaus zu klaus:
Danke, ist mir danach erst aufgefallen. :-)

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:

> Abgesehen davon drohe ich gerade mental in eine Endlosschleife zu
> kommen, weil IoPort abgeleitet wird von einer Liste (IoPort ist
> also eine ganze Liste, nicht ein Port). Diese Liste enthält
> wiederum lauter Elemente vom Typ IoPort, das jeweils eine
> Liste von....

Yep. Das stinkt nach einem Designfehler.

Ein einzelner Port ist nunmal keine Liste von Ports.
Ein PortPool könnte eine Liste von Ports sein, oder ein Manager könnte 
so eine Liste von Ports verwalten. Aber ein Port ist ein Port.

Ein einzelnes Auto ist ja schliesslich auch keine Menge von Autos. Ein 
Parkplatz könnte eine Menge von Autos 'verwalten'. Aber ein Parkplatz 
ist sicherlich kein Auto und hat auch nichts damit zu tun (wird also 
nicht von Auto abegeleitet)

Solche Designfehler funktionieren interessanterweise manchmal sogar 
anfänglich. Aber je weiter das Programm sich entwickelt, desto 
unlogischer wird es dann oft und desto mehr Probleme tauchen auf.
Es ist besser, gleich von vorne herein die Klassenstruktur richtig zu 
haben.

von Klaus W. (mfgkw)


Lesenswert?

Das stinkt nicht nur, es kann gar nicht gehen.
Ein Typ kann doch nicht eine Liste von Elementen seines
Typ enthalten.
Ich mag nicht glauben, daß das so kompiliert wird, egal
wieviel Power dein PowerPC hat.

von Arc N. (arc)


Lesenswert?

Karl heinz Buchegger schrieb:
> klaus schrieb:
>
>> aufzurufen. Was ich nicht verstehe wie man den Parameter "Init" im
>> Template dafür heranziehen kann.
>
> Ich denke da kommen wir der Sache dann schon näher.
> Vermutung: ForEach übernimmt einen Funktionspointer.
>
> Probier mal:
>
>   static void InitAll(void) { ForEach(IoPort::Init); }
>
> Eventuell
>   static void InitAll(void) { ForEach(&IoPort::Init); }
>
> obwohl das IMHO nicht nötig sein sollte.

VC10 meint ohne IoPort:: "function call missing argument list; use 
'&IoPort::Init' to create a pointer to member"
Hilft aber auch nicht weiter solange eine statische Methode eine 
Instanzmethode aufrufen soll.

von klaus (Gast)


Lesenswert?

Nach langem Nachgrübeln kann ich mir kaum vorstellen, dass der gezeigte 
Code Standard konform ist, weil:

Zwar ist die Basisklasse ein Template und wird mit der Ableitung 
instanziiert aber trotz allem bleibt InitAll() static und versucht die 
Adresse einer nicht statischen Member-Funktion Init() zu nehmen, die 
außerdem noch virtuell ist. Wo soll da die Objektreferenz herkommen?

von klaus (Gast)


Lesenswert?

Das ganze ist schon deswegen Quark, weil in der 2. Vorlesung zu 
Objektorientierter Programmierung einem jeder Professor erklären wird, 
dass Ableitungen "ist-Beziehungen" modellieren sollen. Angewandt auf das 
vorliegende Beispiel: "Ist ein IOPort eine verkettete Liste?"

von Klaus W. (mfgkw)


Lesenswert?

oder noch genauer:
"Ist ein IOPort eine verkettete Liste von IoPort-Objekten?"

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:
> Das stinkt nicht nur, es kann gar nicht gehen.
> Ein Typ kann doch nicht eine Liste von Elementen seines
> Typ enthalten.
> Ich mag nicht glauben, daß das so kompiliert wird, egal
> wieviel Power dein PowerPC hat.

Doch, das wird compilieren.
Im List Template werden keine Objekte gespeichert, sondern nur Pointer.

Nichts desto trotz ist das IMHO ein Designfehler.

Und auch der static calls non-static Teil klappt, weil der Listenstart 
First sinnigerweise ebenfalls ein static Member ist.

Oh Mann, da ist so ziemlich alles drinn enthalten, was einem den 
Angstschweiß auf die Stirn treiben wird.

Eine Klasse IoPort, dazu eine verwaltende Managerklasse (ausgeführt als 
Singleton). Wäre wohl zu einfach gewesen :-)

von Karl H. (kbuchegg)


Lesenswert?

Was is'n nun.
Ist der Keil mit der &IoPort::Init Syntax zufrieden?

von Klaus W. (mfgkw)


Lesenswert?

Karl heinz Buchegger schrieb:
> Doch, das wird compilieren.
> Im List Template werden keine Objekte gespeichert, sondern nur Pointer.

Nein:
1
class IoPort : public List<IoPort>
2
...
Da werden keine Zeiger gespeichert, sondern Kopien der
Objekte, mit denen jedes Element initialisiert wird.

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:
> Karl heinz Buchegger schrieb:
>> Doch, das wird compilieren.
>> Im List Template werden keine Objekte gespeichert, sondern nur Pointer.
>
> Nein:
>
1
> class IoPort : public List<IoPort>
2
> ...
3
>
> Da werden keine Zeiger gespeichert, sondern Kopien der
> Objekte, mit denen jedes Element initialisiert wird.

Wo genau?

template<class T>
class List
{
public:
  static T* First;
  static T* Last;
  T* Next;


Ich würde mal sagen: Ein IoPort Objekt besteht aus einem Zeiger auf ein 
weiteres IoPort Objekt (Next). Die beiden static zählen erst mal nicht.
Bis hier hin ist alles legal und in Ordnung.

Da ist im Grunde nichts anderes als eine Spielart von

struct Data
{
  ....
  struct Data* next;
};

nur halt im OOP Gewand.

von Alexander I. (daedalus)


Lesenswert?

Blubb :) Ihr legt ja los! :-)

Also:
Der Code ist so schon seit Jahren exakt so im Einsatz, wurde NICHT von 
mir entwickelt und funktioniert tadellos ohne Warnings usw. Damit werden 
etwa 100-150 IoPorts verwaltet, je nach Systemarchitektur.

Ich finde dieses Konstrukt allerdings ebenfalls höchst seltsam, ich habe 
es in meiner kurzen Karriere bisher noch nicht gesehen. Mich wundert es 
auch, dass der Funktionszeiger auf Instanz->Init() ohne Muh akzeptiert 
wird.
1
static void InitAll(void) { ForEach(IoPort::Init); }
2
static void InitAll(void) { ForEach(&IoPort::Init); }
Der Fehler bleibt weiterhin:
#504-D: nonstandard form for taking the address of a member function

Vielleicht muß ich noch dazu sagen, dass die Instanzen ALLE statisch 
(also kein new/delete) und global sind, es gibt keine new/delete-Orgien. 
Da würde es wohl ziemlich mächtig knallen.

Das das ganze ein Designfehler ist, sehe ich auch so, speziell mit der 
"ist ein"-Argumentation.

von Alexander I. (daedalus)


Lesenswert?

Achso: Mit std::List hat diese Implementierung höchstens den Namen 
gemeinsam.

von Karl H. (kbuchegg)


Lesenswert?

Alexander I. schrieb:

>
1
> static void InitAll(void) { ForEach(IoPort::Init); }
2
> static void InitAll(void) { ForEach(&IoPort::Init); }
3
>
> Der Fehler bleibt weiterhin:
> #504-D: nonstandard form for taking the address of a member function

Eigenartig.
Jetzt weiß ich auch nicht mehr weiter.

Spätestens jetzt würd ich den ForEach wegwerfen und die Schleife selbst 
aufdröseln.
1
  static void InitAll()
2
  {
3
    for( T* node = First; node; node = node->Next )
4
    {
5
      node->Init();
6
    }
7
  }

(Das void in der Argumentliste kannst du dir sparen. Braucht kein 
Mensch)

Das hätte auch den Vorteil, dass leichter zu durchschauen ist, was da 
eigentlich abgeht.

von Klaus W. (mfgkw)


Lesenswert?

Karl heinz Buchegger schrieb:
> Klaus Wachtler schrieb:
>> Karl heinz Buchegger schrieb:
>>> Doch, das wird compilieren.
>>> Im List Template werden keine Objekte gespeichert, sondern nur Pointer.
>>
>> Nein:
>>
1
>> class IoPort : public List<IoPort>
2
>> ...
3
>>
>> Da werden keine Zeiger gespeichert, sondern Kopien der
>> Objekte, mit denen jedes Element initialisiert wird.
>
> Wo genau?

Sorry, ich hatte eine richtige Liste im Kopf, nicht dieses
kranke Gerät hier.
Was wiederum die Frage aufwirft, warum es List heißt,
wenn es nur Zeiger auf anderweitig gespeicherte Objekte
verwaltet (falls wir hier die ganze Liste sehen).

> ...

von Karl H. (kbuchegg)


Lesenswert?

Karl heinz Buchegger schrieb:
> Alexander I. schrieb:
>
>>
1
>> static void InitAll(void) { ForEach(IoPort::Init); }
2
>> static void InitAll(void) { ForEach(&IoPort::Init); }
3
>>
>> Der Fehler bleibt weiterhin:
>> #504-D: nonstandard form for taking the address of a member function
>
> Eigenartig.
> Jetzt weiß ich auch nicht mehr weiter.

Einen hab ich noch
(Klaus, bitte wegschauen)
1
  static void InitAll(void) { ForEach( ((IoPort*)0)->Init ); }
2
3
  static void InitAll(void) { ForEach( &(((IoPort*)0)->Init) ); }

von Alexander I. (daedalus)


Lesenswert?

Die Vergangenheit dieses ganzen "Konstrukts" kenne ich leider nicht. 
Fakt ist, dass es kurioserweise funktioniert. Wenn man das ForEach() weg 
lässt ist die Sache ja geritzt. Aber ich überleg mir das morgen nochmal, 
ob ich wirklich ein "IoPort ist ein IoPort ist ein IoPort ..." haben 
will, oder das anders lösen werde. Danke euch soweit!

von Alexander I. (daedalus)


Lesenswert?

1
static void InitAll(void) { ForEach( &(((IoPort*)0)->Init) ); }
error:  #300: a pointer to a bound function may only be used to call the 
function

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:

> Was wiederum die Frage aufwirft, warum es List heißt,
> wenn es nur Zeiger auf anderweitig gespeicherte Objekte
> verwaltet (falls wir hier die ganze Liste sehen).

Nicht darüber nachdenken.
Die virtuelle abstrakte Funktion Init weist mich darauf hin, das da 
höchst wahrscheinlich auch noch von IoPort abgelitten wird.

Kein virtueller Destruktor, kein Copy Konstruktor, kein Assignment 
Operator. Da ist jemand brandheiss unterwegs :-)

Aber: Abgesehen von dem logischen Designfehler - man kann das so machen.

von Karl H. (kbuchegg)


Lesenswert?

Alexander I. schrieb:
>
1
> static void InitAll(void) { ForEach( &(((IoPort*)0)->Init) ); }
2
>
> error:  #300: a pointer to a bound function may only be used to call the
> function

OK
Keinen blassen Dunst.
Meiner Ansicht nach ist die &IoPort::Init Syntax korrekt und kein Grund 
für den Compiler zum meckern.

von klaus (Gast)


Lesenswert?

Wollte man das Template testen. Verrücktes Stück Code. Ich bekomme es im 
GCC 3.4.2 (ja schon älter ich weiß) nur zum Laufen wenn ich die static 
Variablen explizit anlege:
1
template<class T> T* List<T>::First = 0;
2
template<class T> T* List<T>::Last = 0;

Ansonsten meckert der Linker über "undefined references"...

von Klaus W. (mfgkw)


Lesenswert?

Klar, die müssen definiert sein.
Ist immer so bei static-Elementen einer Klasse.

von Klaus W. (mfgkw)


Lesenswert?

g++ 4.3.2 kompiliert dieses übrigens auch anstandslos mit -Wall
und -Wextra:
1
template<class T>
2
class List
3
{
4
public:
5
  static T* First;
6
  static T* Last;
7
  T* Next;
8
public:
9
  List()
10
  {
11
    T* node = (T*)this;
12
    if(!First) First = node;          // Dies ist das erste Element
13
    if(Last) {Last->List::Next = node;}     // Verkette das letzte Element mit Diesem
14
    Last = node;                      // Dieses ist nun das letzte Element
15
    Next = 0;                         // Es gibt noch kein nächstes Element
16
  }
17
  static void ForEach(void (T::*func)())
18
  {
19
    for(T* node = First; node; node = node->Next)
20
    {
21
      (node->*func)();                // Rufe die zu iterierende Funktion auf
22
    }
23
  }
24
};
25
26
27
class IoPort : public List<IoPort>
28
{
29
public:
30
  static void InitAll(void) { ForEach(&IoPort::Init); } // Fehler!
31
  virtual void Init()
32
  {
33
  }
34
};
35
36
template<class T> T* List<T>::First = 0;
37
template<class T> T* List<T>::Last = 0;
38
39
40
int main()
41
{
42
  IoPort  io;
43
}

von Klaus W. (mfgkw)


Lesenswert?

@khb: ohne das & vor der Funktion geht es übrigens nicht.
(Ich hätte gewettet, daß es keinen Unterschied macht, aber
jetzt natürlich nicht mehr :-)

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:
> @khb: ohne das & vor der Funktion geht es übrigens nicht.
> (Ich hätte gewettet, daß es keinen Unterschied macht, aber
> jetzt natürlich nicht mehr :-)

Die Wette hätten wir beide verloren :-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.