Hallo,
ich habe hier ein größeres Problem der OOP und komme einfach nicht
weiter.
Da ich vermute, dass es mit dem GCC Compiler für ARM zusammenhängen
könnte, schreibe ich es unter Mikrocontroller.
Ich versuche das mal vereinfacht darzustellen.
Ich habe eine Klasse, die von mehreren Basisklassen erbt. Von dieser
Klasse möchte ich mehrere Objekte erzeugen können.
Sowohl meine Klasse als auch die Basisklassen benötigen alle ein Handle
für die einzelnen Methoden.
Dieses Handle wird global erzeugt und wird im Konstruktor als Pointer an
meine Klasse übergeben und von dort aus wird der Pointer des Handle an
die Basisklassen weitergegeben.
Hier mal eine Basisklasse:
pHandle ist jeweils ein Pointer auf den Handle. Dieser wird
durchgereicht.
Soweit so gut.
Jetzt muss ich die Handle anlegen. Hier mache ich eine Deklaration im
Header-File:
1
classConfiguration
2
{
3
private:
4
TIM_HandleTypeDefhtim1_switch;
5
...
Dieser Handle wird dann in einem Konstruktor initialisiert:
1
Configuration::Configuration()
2
{
3
htim1_switch.Instance=TIM1;
4
...
Das Problem:
Ich muss jetzt Objekte der Timerklassen erzeugen, wobei ich den bereits
konfigurierten Handle übergeben muss:
switch(&htim1_switch) ergibt: no match for call to
'(Namespace::MeineKlasse) (TIM_HandleTypeDef*)'
switch = new MeineKlasse(&htim1_switch) ergibt: no match for 'operator='
(operand types are 'Namespace::MeineKlasse' and
'Namespace::MeineKlasse*')
Weiterhin sollen die Timerobjekte außerhalb des Konstruktors natürlich
erhalten bleiben.
Als Compiler verwende ich einen GCC für ARM. Kennt der vielleicht kein
"new" ?
Kann mir da Jemand weiterhelfen?
Vielen Dank!!!
Holger T. schrieb:> Kennt der vielleicht kein "new" ?
Der Compiler kennt es, aber du musst (je nach Umgebung, die dir ggf.
durch IDE etc. vorgegeben wird) u. U. das Backend für malloc()
selbst bereitstellen (die Funktion _sbrk()).
"No match" stammt vom Compiler, da fehlt ihm also irgendeine passende
Deklaration (in einem Headerfile).
Bist du dir sicher, dass du innerhalb von Namespace {} nochmal
"Namespace::" schreiben wolltest? Übermäßig gut kenne ich micht mit
C++ auch nicht aus, aber das sieht mir suspekt aus.
Oh, vielen Dank. Sollte natürlich Basisklasse heißen und nicht
Namespace. Habs geändert. ;)
Ich suche mal, wo ich das "new" her bekomme.
Du könntest Recht haben, dass der das vermisst.
Vielen Dank schonmal!
Kannst Du ein Mickeymausbeispiel wie unten bauen? Was
handle/timer/switch/Timerklasse usw. sind, wird aus der Beschreibung
nicht ganz klar und deine Fehlerzeilen fehlen in den Codeausschnitten.
Und warum man unbedingt new braucht, ist auch nicht klar.
Der Quellcode ist noch wesentlich komplexer mit vielen Schaltern und
vielen Timern. Den Namen "switch" habe ich als Synonym für einen
beliebigen "Schalter" verwendet. Im Originalcode gibt es aber den Namen
"switch" nicht. Daran liegt es nicht.
Es gibt im Prinzip einige Basisklassen und meine Klassen (Es sind Timer
die Pins schalten). Wobei abhängig vom Timer jede meiner Klasse von
anderen Basisklassen erbt.
Dadurch wird das Ganze so kompliziert.
Was nicht geht ist ein Objekt von "MeineKlasse" anzulegen.
Ganz grob gibt es eine Singletonklasse, diese erstellt im Konstruktor
mehrere Instanzen von mehreren verschiedenen Klassen. Diese Klassen
unterscheidet, von welchen Basisklassen diese erben.
Hier gibt es zwei Probleme. Wenn der Konstruktor durch ist sind die
Objekte wider weg. Und das Aufrufen der Konstruktoren führt zu
Fehlermeldungen.
Am liebsten würde ich die Struktur jetzt Global anlegen. Da gibt es aber
das Problem, dass bei der Deklaration im Headerfile bereits ein
Konstruktor ohne Parameter aufgerufen wird.
Holger T. schrieb:> dass bei der Deklaration im Headerfile bereits ein Konstruktor ohne> Parameter aufgerufen wird.
Nein, eine Deklaration "ruft" keinen Konstruktor.
Ein globales (oder statisches) Objekt ruft einen Konstruktor während
der Initialisierung der Applikation, also noch vor main(). Aber das
muss man erstmal definieren, nicht nur seine Klasse deklarieren.
> Wenn der Konstruktor durch ist sind die Objekte wider weg.
Kann auch so nicht sein, da ist noch was anderes foul.
Eine Sache vorneweg: Wie viele pHandle hast Du in Deiner
Klassenhierarchie? Hat jede Klasse ihr eigenes Handel oder nur die
Basisklasse? Wenn es nur die Basisklasse ist musst Du es auch nur dort
initialisieren, und das mach man besser mit der initializer-Syntax:
Des weiteren fehlt, wie oben schon gesagt wurde, die vollständige
Code-Zeile, die den Fehler verursacht. Ich unterstelle mal, sie sieht
irgendwie so aus:
1
Namespace::MeineKlasseswitch(&htim1_switch);
Wie auch schon gesagt ist switch ein Schlüsselwort und das würde ich
grundsätzlich vermeiden (zumindest testweise), auch wenn es hier nicht
das Problem zu sein scheint. Für mich klingt die Fehlermeldung so, als
findet der Compiler den passenden Konstruktor nicht. Du hast zwar oben
eine Definition angegeben, aber ist er auch innerhalb der
Klassendefinition deklariert?
1
classMeineKlasse
2
{
3
public:
4
MeineKlasse(TIM_HandleTypeDef*);// ist diese Zeile da?
5
};
Falls diese Deklaration fehlt müsste allerdings auch die Übersetzung der
von Dir angegebenen Definition mit einer ähnlichen Fehlermeldung
scheitern.
Holger T. schrieb:> Hier gibt es zwei Probleme. Wenn der Konstruktor durch ist sind die> Objekte wider weg.
Das ist natürlich klar. In einer Funktion (und ein Konstruktor ist
letztlich auch nur eine Funktion) lokal angelegte Objekte existieren nur
bis zum Verlassen der Funktion. Das Verwenden von Zeigern oder
Referenzen auf lokale Objekte über die Laufzeit der Funktion hinaus
führt definitiv zu undefiniertem Verhalten. Es sei denn, die Objekte
werden statisch angelegt. Das würde ich aber nicht unbedingt im
Konstruktor machen. Ein static class member wäre in diesem Fall
vielleicht eine geeignete Lösung.
Ich glaub immer noch, dass die erste Antwort richtig is...
Wenn der Threadersteller von _sbrk und Co nichts weiß, dann hats was.
Die newlib-nano enthält halt schlichtweg keine Standard C++
Implementierung der ganzen System Calls.
Ich habe jetzt folgendes versucht:
In der Headerdatei der Singleton-Klasse "Configuration" deklariere ich
für jedes zukünftige Objekt einen Handle und das Objekt selbst.
1
TIM_HandleTypeDefhandle_Schalter1;
2
MeineKlasseschalter1;
In der C-Datei in einer Init-Funktion (Configuration::Init())
initialisiere ich das Handle und das Objekt.
1
handle_Schalter1.Instance=TIM1;
2
MeineKlasseschalter1(&handle_Schalter1);
Für jedes Objekt, welches ich anlege gibt es eine Fehlermeldung im
Konstruktor "Configuration".
1
Configuration::Configuration()// Hier werden die Fehler angezeigt
2
{
3
}
Die Meldung lautet:
no matching function for call to 'Namespace::MeineKlasse::MeineKlasse()'
Configuration::Configuration()
Also irgendwie will der Compiler im Konstruktor Configuration() meine
Klassen jeweils mit einem Standardkonstruktor aufrufen. Ich kann mir
aber leider nicht erklären warum.
Zimmere doch bitte mal ein minimales compilierbares (bzw. eben
nicht compilierbares) Beispiel.
Irgendwie werde ich das Gefühl auch nicht los, dass das alles mächtig
„von hinten durch die Brust ins Auge“ gezimmert ist, aber warum du
die Architektur so gewählt hast, wirst du sicher selbst wissen.
Der Fehler wird im Konstruktor Konfiguration() angezeigt:
no matching function for call to
'Namespace::MeineKlasse1::MeineKlasse1()'
Configuration::Configuration()
Jörg W. schrieb:> Zimmere doch bitte mal ein minimales compilierbares (bzw. eben> nicht compilierbares) Beispiel.
... soll hier im mikrocontroller.net bedeuten: Ein vollständiges,
minimalistisches Programm, welches fast nichts weiter als den Fehler
enthält. Am besten nur eine Datei als Anhang.
Holger T. schrieb:> Ich habe jetzt mal den Code auf ein Minimum reduziert.
Da fehlen mir jetzt immer noch all die STM32-Headers dafür.
Kannst du die nicht irgendwie rauskicken und durch irgendwas
abstraktes "simulieren"?
> // Folgende Zeilen gehen nicht, weil der Compiler "new" nicht moechte> // highSideA = new TimerSlaveOCBreak(&htim1_highSideA);> // stimTimeBase = new TimerUseMaster(&htim2_stimTimeBase);
Jetzt weiß ich zumindest, woher deine Überschrift des Threads kommt.
Aber deine blumige Ausdrucksweise hilft nicht weiter. Warum sollte
der Compiler "new" denn "nicht mögen"? Welche Fehlermeldung bekommst
du denn?
Wie schon geschrieben worden ist, das Backend für new ist ein
schnödes malloc(), und dieses wiederum setzt in der newlib typisch
auf _sbrk() auf, welches du als Anwender liefern musst.
Mit der newlib nano habe ich noch nicht viel gemacht, aber musst du
die denn benutzen, kannst du nicht auch die Standard-newlib nehmen?
> Der Fehler wird im Konstruktor Konfiguration() angezeigt:
Bei welcher Compiler-Kommandozeile?
Sorry, es sind zwar schon viele Stückchen da, aber nichts, was ich
hier unabhängig von deiner Umgebung auch mal bei mir ausprobieren
könnte.
Holger T. schrieb:> // Folgende Zeilen gehen nicht, weil der Compiler "new" nicht moechte> // highSideA = new TimerSlaveOCBreak(&htim1_highSideA);
Wo und wie ist highSideA in diesem Fall deklariert?
> // Darum alternativ so:> TimerSlaveOCBreak highSideA(&htim1_highSideA);Jörg W. schrieb:> Irgendwie werde ich das Gefühl auch nicht los, dass das alles mächtig> „von hinten durch die Brust ins Auge“ gezimmert ist,
Mja, geht mir auch so.
Configuration::Configuration()// Hier im Konstruktor wird der Fehler angezeigt
14
{
15
16
}
17
18
voidConfiguration::TimerInit()
19
{
20
handle.Instance=TIM1;
21
22
// Folgende Zeile geht nicht, weil der Compiler "new" nicht moechte
23
// meineKlasse1 = new MeineKlasse1(&handle);
24
25
// Darum alternativ so:
26
MeineKlasse1meineKlasse1(&handle);
27
}
28
29
}/* namespace Namespace */
Das obige Beispiel hat noch nicht die Namen angepasst und war nicht
minimal.
Es handelt sich um eine Konfiguration, eine Basisklasse und eine
abgeleitete Klasse. Jeweils mit cpp und h-file.
Das ist jetzt wirklich das Minimum.
Hallo,
bei der Verwendung von "new" erhalte ich folgende Fehlermeldung:
no match for 'operator=' (operand types are 'Namespace::Meineklasse1'
and 'Namespace::Meineklasse1*')
meineKlasse1 = new MeineKlasse1(&handle);
Der Compileraufruf wird folgendermaßen aufgerufen:
arm-none-eabi-g++ -mcpu=cortex-m3 -mthumb -Og -fmessage-length=0
-fsigned-char -ffunction-sections -fdata-sections -Wl,-gc-sections
-DDEBUG -DUSE_FULL_ASSERT -DTRACE -DSTM32F215xx -DUSE_HAL_DRIVER
-DHSE_VALUE=8000000 -Wall -Wextra -g3
Auch mir kommt das Ganze wie von hinten durch die Brust geschossen vor,
aber es muss doch eine Lösung geben.
Das Schwierige ist eben, dass die Objekte zum Anlegen bereits ein
konfiguriertes Handle benötigen. Daher kann ich die Objekte zum
Programmstart leider noch nicht anlegen.
Wie ist meineKlasse1 deklariert? Von der Fehlermeldung her sieht aus als
wenn's
Namespace::Meineklasse1 meineKlasse1 ist, und nicht
Namespace::Meineklasse1 *meineKlasse1.
Der Compiler beschwert sich, dass er einer nicht-Pointer-Variable einen
Pointer zuweisen soll.
Holger T. schrieb:> Hallo,>> bei der Verwendung von "new" erhalte ich folgende Fehlermeldung:> no match for 'operator=' (operand types are 'Namespace::Meineklasse1'> and 'Namespace::Meineklasse1*')> meineKlasse1 = new MeineKlasse1(&handle);
Dann musst du einen Zuweisungsoperator implementieren (also sowohl
deklarieren als auch definieren).
Die Fehlermeldung ist auch suspekt: links steht eine Klasse, rechts
ein Zeiger auf die Klasse?
Eventuell hilft es auch schon, wenn du den Konstruktor anders
schreibst:
1
classMeineKlasse1:publicBasisklasse1
2
{
3
private:
4
TIM_HandleTypeDef*pHandle;
5
6
public:
7
MeineKlasse1(TIM_HandleTypeDef*handle):
8
Basisklasse1(handle),
9
pHandle(handle)
10
{};
11
12
virtual~MeineKlasse1();
13
};
Also für pHandle die Konstruktor-Syntax statt einer Zuweisung
nehmen, und eigentlich kann das dann auch gleich inline in den
Header.
> Der Compileraufruf wird folgendermaßen aufgerufen:> arm-none-eabi-g++ -mcpu=cortex-m3 -mthumb -Og -fmessage-length=0> -fsigned-char -ffunction-sections -fdata-sections -Wl,-gc-sections> -DDEBUG -DUSE_FULL_ASSERT -DTRACE -DSTM32F215xx -DUSE_HAL_DRIVER> -DHSE_VALUE=8000000 -Wall -Wextra -g3
Da fehlt der Name der C++-Datei. Das wäre eine der Fragen gewesen,
welche Datei ich hier überhaupt compilieren muss, um dein Problem
nachzuvollziehen. Aber wie oben schon geschrieben, es gibt in deinem
Beispiel noch so viele Dinge, die ich hier nicht herumliegen habe,
dass ich das ohnehin nicht selbst probieren kann.
Holger T. schrieb:> Dieses Handle wird global erzeugt und wird im Konstruktor als Pointer an> meine Klasse übergeben und von dort aus wird der Pointer des Handle an> die Basisklassen weitergegeben.
Pointer auf Handle? Das klingt erst einmal total schräg, da ein Handle
üblicherweise schon so etwas wie ein Pointer ist. Ein Handle sollte auf
jeden Fall folgende Eigenschaften haben:
- klein
- leicht zu kopieren
> Hier mal eine Basisklasse:> Basisklasse::Basisklasse(TIM_HandleTypeDef *handle)> {> pHandle = handle;> }
benutze doch Initialisierung, wenn Du initialisieren möchtest:
Ok, ich nehme alles zurück, TIM_HandleTypeDef ist sicher kein handle. ST
ist sich aber auch keine gute Quelle um Software-Entwicklung zu lernen
;-)
Wenn handle nie 0 sein sollte, dann zwingt sich ggf. eine Referenz auf
(oder sogar eine const reference).
Ich fänd' dass "timer" ein passenderer Name wäre.
> Das Problem:> Ich muss jetzt Objekte der Timerklassen erzeugen, wobei ich den bereits> konfigurierten Handle übergeben muss:> switch(&htim1_switch) ergibt: no match for call to> '(Namespace::MeineKlasse) (TIM_HandleTypeDef*)'
Wo kommt jetzt switch her? switch ist ein reserviertes Wort in C++.
> switch = new MeineKlasse(&htim1_switch) ergibt: no match for 'operator='> (operand types are 'Namespace::MeineKlasse' and> 'Namespace::MeineKlasse*')
Ja, auf der linken Seite steht so etwas int und auf der rechten Seite so
etwas wie int*. Passt nicht! Ansonsten code, den Du im Forum zeigen
möchtest immer kopieren, nie abschreiben (siehe htim1_switch/switch)!
> Kann mir da Jemand weiterhelfen?
Bei Fehlermeldungen immer ganz genau hin gucken. In der Regel steht da
schon, was das Problem ist. In Deinem Fall, versuchst Du halt eine
Initialisierung oder Zuweisung mit nicht kompatiblen Typen.
mfg Torsten
Jörg W. schrieb:> aber warum du die Architektur so gewählt hast, wirst du sicher selbst> wissen.
Komische Software-Architekturen entstehen bisweilen auch und gerade
deshalb, weil jemand nicht so genau weiß, was er da tut. ;-)
Holger T. schrieb:> Configuration& Configuration::getInstance()
Juhu ein Singleton! :'(
> {> static Configuration instance;> return instance;> }>> Configuration::Configuration() // Hier im Konstruktor wird der Fehler> angezeigt> {
Genau: Configuration hat ein MeineKlasse1 als member. Und MeineKlasse1
hat keinen default c'tor mit dem die Instanz von MeineKlasse1
initialisiert werden kann.
Vererbung ist so ziemlich die stärkste Kopplung, die man haben kann.
Bist Du Dir sicher, dass Du Dir das antun möchtest?
Habe eine Lösung gefunden! :-) :-)
Mir wurde mitgeteilt, dass die angebliche Deklaration im Headerfile in
wirklichkeit schon den Konstruktor aufruft. Und zwar den
Standardkonstruktor.
Mir wurde auch empfohlen daraus Pointer zu machen.
In der Initialisierung hat dann das "new" plötzlich funktioniert.
Die Objekte werden jetzt also dynamisch angelegt und die Pointer sorgen
dafür, dass ich weiß wo ich hin schreibe.
Dies steht im Headerfile:
1
TIM_HandleTypeDefhandle1;
2
MeineKlasse1*schalter1;
Und die Implementierung:
1
handle1.Instance=TIM1;
2
schalter1=newMeineKlasse1(&handle1);
Und siehe da, es läuft jetzt. Zumindest kompiliert er, den Rest muss ich
noch testen. :-)
Vielen Dank für die vielen Posts! Ich konnte viel lernen.
Holger T. schrieb:> Mir wurde auch empfohlen daraus Pointer zu machen.> In der Initialisierung hat dann das "new" plötzlich funktioniert.
Schauder :-|
> Die Objekte werden jetzt also dynamisch angelegt und die Pointer sorgen> dafür, dass ich weiß wo ich hin schreibe.
Der ganze HeckMac, damit Du dieses Singleton Antipattern verwenden
kannst?
Dann erwarte ich eigentlich, dass der Wert vom MasterOutputTrigger (hier
32) auch in die Variable OR-Verknüpft wird.
htim->Instance->CR2 bleibt aber 0.
Wobei htim auf 0x20000284 zeigt und htim->Instance auf 0x40000000.
Holger T. schrieb:> htim->Instance->CR2 bleibt aber 0.
Was je nach Bitmuster auch durchaus ein korrektes Ergebnis sein kann.
Hast Du einen Debugger, mit dem Du das Programm mal durchsteppen kannst?
Dann mach das. Zum Beispiel gibt es den GDB auch für ARM.
aufgerufen wird, und sMasterConfig->MasterOutputTrigger ist dabei 32,
dann müsste dies durch die ODER-Verknüpfung eigentlich in der Struktur
stehen.
Es bleibt aber weiterhin 0.
Vielleicht stelle ich das Problem später ins Forum unter einem anderen
Namen. Mit dem Ursprungsproblem hat das ja nichts mehr zu tun.
Holger T. schrieb:> Mir wurde mitgeteilt, dass die angebliche Deklaration im Headerfile in> wirklichkeit schon den Konstruktor aufruft. Und zwar den> Standardkonstruktor.>> Mir wurde auch empfohlen daraus Pointer zu machen.> In der Initialisierung hat dann das "new" plötzlich funktioniert.>> Die Objekte werden jetzt also dynamisch angelegt und die Pointer sorgen> dafür, dass ich weiß wo ich hin schreibe.
vom "new" gibt es zwei Varianten:
die "normale" Version holt sich dynamischen Speicher für die Größe des
zu erzeugenden Objekts, ruft den Konstruktor auf, und retourniert einen
Zeiger auf das erzeugte Objekt
die "placement" Version bekommt einen Pointer mit, ruft den Konstruktor
auf, und retourniert den Zeiger auf das erzeugte Objekt. Kommt im
"normalen" Leben eher selten vor, Anwendungen sind IMHO auf
Optimierungen oder Spezialfälle (Container) beschränkt.
"new" ist also quasi synonym mit "Objekt ist dynamisch angelegt". Man
kann zwar "placement new" auf eine lokale oder statische Variable
anwenden, also sowas wie "Objekt o; new(o) Objekt;", das hat aber keinen
Sinn, sollte m.E. ein Error sein, und mir ist in über 20 Jahren C++
sowas noch nicht untergekommen, daher bin ich mir auch ob der
syntaktischen Korrektheit nicht sicher.
auf alle Fälle:
1
Objekto=newObjekt;// ist falsch
2
3
Objekt*o2=newObjekt;// richtig
sonst noch aufgefallen:
* Ein Singleton sollte man eigentlich vermeiden, zur Repräsentation von
Hardware mags ja Berechtigung haben, dann kann man das aber anders
implementieren, so dass man ohne das dauernde "getInstance" auskommt.
Grund für virtuelle Methoden etc... gibts bei einem solchen Singleton eh
per Definition nicht, das muss alles zur Compile-Zeit feststehen.
* Mehrfach von verschiedenen Basisklassen zu erben, die dann alle einen
Zeiger auf das selbe Handle vorrätig halten, verschwendet Platz.
Virtuelle Basis-Klasse zum Handle-Speichern würde gehen, hat aber
möglicherweise andere Nachteile.
* Eleganter geht sowas mit templates und dem "CRTP", also sowas a la
1
template<classC>
2
classBasis
3
{
4
public:
5
Basis(){};
6
do_something(){C.getTimer()->start();}
7
}
8
9
classKlasse
10
:publicBasis<Klasse>
11
{
12
public:
13
Klasse(){m_timer=newTimer();};
14
Timer*getTimer(){return&m_timer;}
15
16
private:
17
Timer*m_timer;
18
}
* Das speichert das Handle nur einmal
* Basisklassen kann man viele machen
* Zusammengestrickt wird alles erst in der konkreten "Klasse", und zwar
schon zur Compile-Zeit, es gibt also keinen Laufzeit-Overhead
addendum: das "new Timer" ist nur ein Beispiel, das brauchts so auch
nicht.