CPP Problem ... ich verzweifel noch

Der chaotische Hauptfaden

Moderatoren: Heaterman, Finger, Sven, TDI, Marsupilami72, duese

Antworten
MSG
Beiträge: 2195
Registriert: Fr 9. Nov 2018, 23:24
Wohnort: Nähe Dieburg

CPP Problem ... ich verzweifel noch

Beitrag von MSG »

Hallo Gemeinde,

evtl ist es hier offtopic, aber allgemein ist es ja :-)

Ich spiel gerade mit dem Arduino und versuche mich da in OOP. In ABAP oder Java klappt das ja einigermaßen, aber hier habe ich gerade ein Problem.

ich habe eine klasse, die das Adafruit Motor Shield (AFS) verwenden soll. Dummerweise brauchen die Konstruktoren in der Klasse immer irgendwelche Daten, z.B. die Motornummer.

Meine Klasse:
class Fahrzeug (byte motorNummer){
private: byte motorNummer;
/// (1) Hier soll jetzt auch noch das Objekt für das AMS hin
public: Fahrzeug(byte Nr){ // Kontruktor
motorNummer = Nr;
// (2) Hier müsste jetzt das Objekt für das AMS mit der Motornummer irgendwie erzeugt werden.
}
}

In Spaghetticode erzeugt man den Motor so:
AF_DCMotor ersterMotor(motorNummer, MOTOR12_64KHZ);

Bei (1) kann ich das aber nicht machen, da ja die Motornummer da noch gar nicht bekannt ist.
Versuche in 1 sowas zu machen: AF_DCMotor ersterMotor; und in (2) dann ersterMotor(1, MOTOR12_64KHZ) funktionieren nicht... weil bei (1) ja der Konstruktor von AF_DCMotor aufgerufen wird und dem dann die Parameter fehlen.

Hat da jeman dein Tipp?
manuel
Beiträge: 767
Registriert: Fr 7. Feb 2014, 00:14

Re: CPP Problem ... ich verzweifel noch

Beitrag von manuel »

Macht man sowas in C++ nicht mit templates ?
Benutzeravatar
Lukas
Beiträge: 157
Registriert: Mo 12. Aug 2013, 18:45

Re: CPP Problem ... ich verzweifel noch

Beitrag von Lukas »

In lesbar ;)

Code: Alles auswählen

class Fahrzeug (byte motorNummer){
private: 
    byte motorNummer;
     /// (1) Hier soll jetzt auch noch das Objekt für das AMS hin 
public: 
    Fahrzeug(byte Nr){ // Kontruktor
        motorNummer = Nr;
        // (2) Hier müsste jetzt das Objekt für das AMS mit der Motornummer irgendwie erzeugt werden.
    }
}
Eine Lösung ist bei (1) einen smart pointer anzulegen und dann im Konstruktor das Objekt zu erzeugen.
z.B.:

Code: Alles auswählen

class Fahrzeug{
private: 
    byte motorNummer;
    shared_ptr<AF_DCMotor> ersterMotor;
public: 
    Fahrzeug(byte Nr){ // Kontruktor
        motorNummer = Nr;
        ersterMotor = make_shared<AF_DCMotor>(motorNummer, MOTOR12_64KHZ);
    }
}
MSG
Beiträge: 2195
Registriert: Fr 9. Nov 2018, 23:24
Wohnort: Nähe Dieburg

Re: CPP Problem ... ich verzweifel noch

Beitrag von MSG »

Hallo Lukas,

erst mal danke für die lesbare Formatierung :-)

leider gibt die Zeile:

Code: Alles auswählen

    shared_ptr<AF_DCMotor> ersterMotor;
schon alleine beim complieren die Fehlermeldung "shared_ptr is not a template"

Und ich google mir schon seit gestern den Wolf, finde aber keine einigermaßen verständliche Lösung für mein Problem. Das mit den Templates scheint in die richtige Richtung zu gehen. Aber ich glaube, ich weiß jetzt warum CPP aussterben wird ;)

Ich habs dann noch mit Vererbung probiert in der Art

Code: Alles auswählen

class My_DCMotor : public AF_DCMotor {
    public:  My_DCMotor() { // DefaultKonstruktor
       // tu nichts
    }
    public:  My_DCMotor( int nr, int modus) { 
       AF_DCMotor(nr, modus);  // Aufruf des Vater-Konstruktors
    }

};
Das scheitert aber schon daran, dass das verf"$%"$§ CPP hier schon beim erzugen der Klasse My_DCMotor auch den Konstruktor von AF_DCMotor ausführen möchte...
Benutzeravatar
ferdimh
Beiträge: 9413
Registriert: Fr 16. Aug 2013, 15:19

Re: CPP Problem ... ich verzweifel noch

Beitrag von ferdimh »

Ich glaube, du hast zu viel Java o.Ä. benutzt...
Instanzierung eines Objekts im Stile

Code: Alles auswählen

Typ Instanz;
macht man generell nicht.
Dem Konstruktor könnte man zwar per

Code: Alles auswählen

Typ Instanz(Parameter,Parameter,noch mehr Parameter);
Parameter mitgeben (so dass dann auch der richtige aufgerufen wird), aber man macht das Trotzdem nicht, weil das Objekt dann auf dem Stack landet (und dort ist der Platz knapp).
Man verwendet Pointer, dann sieht das fast nach Java aus (und funktioniert auch genauso):

Code: Alles auswählen

Typ *Pointer;
Pointer = new Typ(Parameter...);
Wenn man damit fertig ist, muss man das Ganze dann per delete entsorgen.
Was du da mit Templates willst, ist mir noch nicht ganz klar.
MSG
Beiträge: 2195
Registriert: Fr 9. Nov 2018, 23:24
Wohnort: Nähe Dieburg

Re: CPP Problem ... ich verzweifel noch

Beitrag von MSG »

Hi ferdim,
ferdimh hat geschrieben:Was du da mit Templates willst, ist mir noch nicht ganz klar.
Prima, mir war das nämlich auch nicht klar ;-)

Aber der Tip mit den Pointern wars. Dieses Beispiel funktioniert:

Code: Alles auswählen

class Reifen {
  private:
    String hersteller;
    String profil;
  public:
    Reifen(String herst, String prof){  // constructor
      hersteller = herst;
      profil = prof;
    }
    void bericht() {
      Serial.print("Hersteller: ");
      Serial.print(hersteller);
      Serial.print(" Profil: ");
      Serial.println(profil);
    }
};

class Auto {
  private: 
    // hier sollen jetzt die Reifen (=Objekte) rein für rv, lv, rh, rl
    Reifen *rv;
    Reifen *lv;
    Reifen *rh;
    Reifen *rl;
    byte dummy;
  public:
    void initalisiere_reifen(){
      rv = new Reifen("Bridgestone", "gut");
      lv = new Reifen("Dunlop",      "gut");
      rh = new Reifen("Fulda",       "schlecht");
      rl = new Reifen("Yokohama",     "naja");
    };
    void reifen_bericht(){
      delay(4000);
      rv->bericht();
      lv->bericht();
      rh->bericht();
      rl->bericht();
    };
};


Auto *car;
car = new Auto();
car->initalisiere_reifen();
car->reifen_bericht();
Danke euch allen nochmal fürs Augen öffnen.

Und Java... naja ehrlich gesagt programmierte ich früher in bash und PHP und seit 20 Jahren verdiene ich meine Brötchen mit ABAP.
C hatte ich mal 2 Wochen 1998 gelernt und seither nichts mehr gemacht und C++ .... well... eigentlich noch gar nichts.

Am schlimmsten find ich eigentlich, dass die ganzen Bücher über OOP (egal ob C++ oder Java) von irgendwelchen Theoretikern geschrieben werden (ja ich hab auch eins und da kräftig nachgelesen, deine Lösung stand nicht drin) und praktische Beispiele oder gar Tipps fürs tägliche Leben da total untergehen :(
Benutzeravatar
ferdimh
Beiträge: 9413
Registriert: Fr 16. Aug 2013, 15:19

Re: CPP Problem ... ich verzweifel noch

Beitrag von ferdimh »

Das ist ein Fehler in den sogar Dozenten in "Grundlagen der Informatik" immer weider reinrennen.
Ich hatte Java unterstellt, weil Java eben genau diesen Unterschied versteckt, wodurch dieser Fehler ständig passiert, wenn man auf die Realität losgelassen wird.
Hier geht das so:

Code: Alles auswählen

int Variable; //Basisdatentyp: Stelle sofort Speicher bereit
Trinitrotoluol Boeller; //Hier wird nur eine Referenz erzeugt! 
C++ trennt das explizit (wie mir sinnvoll erscheint).

Im Übrigen funktioniert dein String genauso: Es gibt die Klasse String. Mit

Code: Alles auswählen

String hersteller;
Erledigst du gleich alles (aufm Stack) und hast eine vollfunktionsfähige Instanz der Klasse String, die man mal kurz benutzen kann. So wie man es immer mit Basisdatentypen macht.
Es gibt dann auch noch Referenzen, die wie Variablen aussehen, sich aber wie Pointer verhalten, aber hier wäre ich vorsichtig. Da kriegt man schnell nen Knoten im Hirn.
Benutzeravatar
Lukas
Beiträge: 157
Registriert: Mo 12. Aug 2013, 18:45

Re: CPP Problem ... ich verzweifel noch

Beitrag von Lukas »

Vorsicht mit den Pointern!
Mit new belegst du Speicher der in deinem Beispiel nie wieder freigegeben wird. Normalerweise macht man das entweder im Destruktor oder man verwendet smart pointer(das Objekt wird gelöscht wenn keine Referenz darauf existiert).
Deshalb habe ich shared_ptr vorgeschlagen. Diese Pointer sind seit c++11 Teil von STL ( std::shared_ptr). Ich habe aber überlesen das es um Arduinos geht und daher wirst du STL nicht verwenden können/wollen.

Ich würde die Reifen im Konstruktor erzeugen und dann im Destruktor löschen. Dadurch kann man nicht unabsichtlich reifen_bericht vor initalisiere_reifenauf aufrufen und der Speicher wird korrekt freigegeben. Also:

Code: Alles auswählen

class Auto {
  private: 
    // hier sollen jetzt die Reifen (=Objekte) rein für rv, lv, rh, rl
    Reifen *rv;
    Reifen *lv;
    Reifen *rh;
    Reifen *rl;
    byte dummy;
  public:
    Auto(){
        rv = new Reifen("Bridgestone", "gut");
        lv = new Reifen("Dunlop",      "gut");
        rh = new Reifen("Fulda",       "schlecht");
        rl = new Reifen("Yokohama",     "naja");
    };
    
    ~Auto(){
         delete rv;
         delete lv;
         delete rh;
         delete lh;
     }

    void reifen_bericht(){
      ...
    };
};
MSG
Beiträge: 2195
Registriert: Fr 9. Nov 2018, 23:24
Wohnort: Nähe Dieburg

Re: CPP Problem ... ich verzweifel noch

Beitrag von MSG »

ferdimh/Lukas: Ihr seid echt klasse. Ich glaube, ich such mal nach einem Buch für Praktiker. Für Theoretiker hab ich ja schon eins ;-(

@Lukas: Ja stimmt an das Freigeben hatte ich da gar nicht mehr wirklich gedacht, da in meinem Anwendungsfall alle Klassen genau einmal existieren so lange das Programm läuft. Aber das muss ja nicht immer so sein.

LG, Mathias
audax
Beiträge: 71
Registriert: Do 5. Jan 2017, 14:29

Re: CPP Problem ... ich verzweifel noch

Beitrag von audax »

Es gibt noch eine einfache Lösung, wenn Du keinen dynamischen Speicher verwenden kannst/möchtest:

Code: Alles auswählen

class Motor
{
   public:
      Motor( int Nr);
      // ...
};

class MyClass
{
     Motor _motor;  // Member-Deklaration

public:
   MyClass( int Nr )
      : _motor( Nr )  // Initialisierungsliste
   {
      // ...
   }
};
Zur Erklärung: Das Member-Objekt (_motor) wird nicht bei seiner Deklaration innerhalb der Klasse erzeugt, sondern unmittelbar bevor der {}-Block deines Konstruktors ausgeführt wird. Das nennt sich Initialisierung. Wenn Du das Objekt nicht in der Initialisierungsliste (der Teil hinter dem Doppelpunkt) aufführst, versucht der Compiler, den Default-Konstruktor zu verwenden, den es für die Klasse Motor aber nicht gibt -> Fehler. Wenn Du statt dessen eine Initialisierung angibst, ist alles gut.

Die Sache mit Pointer/dynamischem Speicher kann sinnvoll sein (Stichwort pimpl), aber es hat auch Vorteile, das nicht zu machen. Z.B. hat man nicht auf jedem (embedded) System dynamischen Speicher zur Verfügung. Außerdem wird so das Motor-Objekt direkt in das MyClass-Objekt eingebettet, liegt also zusammenhängend im Speicher. Das kann den Zugriff schneller machen (Cache/Prefetch), außerdem spart man eine Pointer-Dereferenzierung, den Speicherplatz für den Pointer und die Ausführungszeit für das Speichermanagement (new+delete).

Edith ist noch eingefallen: Wenn man schon Pointer verwendet, gibt es mehrere Fallstricke zu beachten. Der erste ist das Kopierverhalten der Klasse (tiefe vs. flache Kopie). Sollen alle Kopien eines Objektes auf die gleiche Instanz der Membervariable zugreifen? Normalerweise wahrscheinlich eher nicht. Was passiert beim Zerstören einer Kopie? Im Zweifel würde ich hier erst mal std::unique_ptr verwenden, wenn nicht explizit ein flaches Kopierverhalten beabsichtigt ist.

Die Variante mit new/delete und mehreren Membervariablen kann zu Speicherlecks führen, wenn in einem der Konstruktoraufrufe eine Exception auftritt. Das ist quasi das Lehrbuch (negativ-)Beispiel, warum man Smart Pointer verwenden soll.
Antworten