class Vererbungen kürzbar?

Hallo,

kann man die Vererbungen “einkürzen”?

Die Methode long getRelCounter() gibt es nur in den Vererbungen und ist dort unverändert gleich. Die Basisklasse GrayEncoder soll diese jedoch nicht besitzen. Muss dass hier demnach doppelt sein also 1:1 mit geschleppt werden?

Die encode() Methode ist wird in den Vererbungen nur um einen Methodenaufruf ergänzt. Muss auch hier der Grundstock von encode() mitgeschleppt werden oder geht das noch einfacher bzw. kürzer?

Auch die rein interne Methode update_relCounter() unterscheidet sich nur im halbieren oder vierteln des Absolutwertes.

Der Kram mit dem relativen Zähler soll es nur in den Vererbungen geben.

Im Grunde möchte ich eine Allzweckklasse für alle meine Encoder. Wie würdet ihr es sonst machen? Was kann man anders bzw. besser machen?

class GrayEncoder       // Klassenname "GrayEncoder"
{
  protected:                    // private wird zu protected, nur intern zugängliche Elemente ...
    volatile byte const *PORT;  // verwendeter Port
    byte const PinA;            // Pin Port.Bit erste Phase
    byte const PinB;            // Pin Port.Bit zweite Phase
    int relMin;
    int relMax;
    volatile long rel_counter;  // relativer Zähler, veränderbar
    volatile long abs_counter;  // absoluter Zähler, nicht veränderbar
    int direction;              // Richtungsanzeige  +1 / -1
    int enc_delta;
    int last;
    byte Phase_A;               // erste Phase
    byte Phase_B;               // zweite Phase


  public:              // von außen zugängliche Elemente ...
    GrayEncoder (volatile byte *p_Port, byte _A, byte _B):
      // Initialisierungsliste
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      relMin(0),
      relMax(0),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }

    GrayEncoder (volatile byte *p_Port, byte _A, byte _B, int _min, int _max):
      // Initialisierungsliste
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      relMin(_min),
      relMax(_max),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }
    
    void init()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;

      last = n;                       // power on state
      enc_delta = 0;
    }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;          // convert gray to binary

      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (diff & 2) - 1;             // bit 1 = direction (+/-), Zähler
        abs_counter += (long) enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }
    }

    int getDirection()  {return direction; }      // Richtungsabfrage
    
    long getAbsCounter()  {return abs_counter; }  // absoluten Zählwert abfragen                
   
    int getA()  { return Phase_A; }               // Signal von Phase A
    
    int getB()  { return Phase_B; }               // Signal von Phase A
};

// Klasse "ALPS_EM20B" erbt von "GrayEncoder" aber nur mit dem zweiten Konstruktor
class ALPS_EM20B : public GrayEncoder
{
  public:               // von außen zugängliche Elemente ...
    ALPS_EM20B(volatile byte *p_Port, byte _A, byte _B, byte _min, byte _max): GrayEncoder (p_Port, _A, _B, _min, _max)
    { }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;          // convert gray to binary

      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (diff & 2) - 1;             // bit 1 = direction (+/-), Zähler
        abs_counter += (long) enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }

      update_relCounter();
    }

    void update_relCounter () {
      static long abs_old = 0;
      long new_abs = abs_counter>>2;    // geviertelt, 4 Steps pro Rastung
      
      if ( new_abs != abs_old) {
        if (new_abs > abs_old) {
          rel_counter++;
        }
        else {
          rel_counter--;
        }
        abs_old = new_abs;
      }

      if (rel_counter < relMin) {
        rel_counter = relMax;
      }
      else if (rel_counter > relMax) {
        rel_counter = relMin;
      }
    }
    
    long getRelCounter()   {            // relativen Zählwert abfragen
      return rel_counter;               // wird schon geviertelt +/- geändert
    }
    
    long getAbsCounter()   {            // absoluten Zählwert abfragen
      return abs_counter >> 2;          // 4 Steps pro Rastung
    }
};

// Klasse "ALPS_EC11" erbt von "GrayEncoder" aber nur mit dem zweiten Konstruktor
class ALPS_EC11 : public GrayEncoder
{
  public:               // von außen zugängliche Elemente ...
    ALPS_EC11(volatile byte *p_Port, byte _A, byte _B, byte _min, byte _max): GrayEncoder (p_Port, _A, _B, _min, _max)
    { }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;          // convert gray to binary

      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (diff & 2) - 1;             // bit 1 = direction (+/-), Zähler
        abs_counter += (long) enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }

      update_relCounter();
    }

    void update_relCounter () {
      static long abs_old = 0;
      long new_abs = abs_counter >> 1;          // halbiert, 2 Steps pro Rastung
      
      if ( new_abs != abs_old) {
        if (new_abs > abs_old) {
          rel_counter++;
        }
        else {
          rel_counter--;
        }
        abs_old = new_abs;
      }

      if (rel_counter < relMin) {
        rel_counter = relMax;
      }
      else if (rel_counter > relMax) {
        rel_counter = relMin;
      }
    }
    
    long getRelCounter()   {                // relativen Zählwert abfragen
      return rel_counter;                   // wird schon halbiert +/- geändert
    }
    
    long getAbsCounter()   {                // absoluten Zählwert abfragen
      return abs_counter >> 1;              // 2 Steps pro Rastung
    }
};

// ---------------------------------------------------------------------------------------

GrayEncoder Encoder1 (&PINC, 0, 1);         // Phase A/B, Pin 37/36 > Port.C Bit.0 / Bit.1
ALPS_EM20B  Encoder2 (&PINC, 0, 1, 0, 10);  // Phase A/B, Pin 37/36 > Port.C Bit.0 / Bit.1 / RelMin / RelMax
ALPS_EC11   Encoder3 (&PING, 1, 0, 0, 10);  // Phase A/B, Pin 40/41 > Port.G Bit.1 / Bit.0 / RelMin / RelMax


int Encoder1_Direction;
int Encoder1_AbsCounter;
byte Encoder1_PhaseA;
byte Encoder1_PhaseB;

int Encoder2_Direction;
int Encoder2_RelCounter;
int Encoder2_AbsCounter;

int Encoder3_Direction;
int Encoder3_RelCounter;
int Encoder3_AbsCounter;


void setup() {
  Serial.begin(250000);

  pinMode(36, INPUT_PULLUP);
  pinMode(37, INPUT_PULLUP);
  pinMode(40, INPUT_PULLUP);
  pinMode(41, INPUT_PULLUP);
  Encoder1.init();
  Encoder2.init();
  Encoder3.init();
}

void loop() {

  update_Encoder();

  Ausgabe();
 
}

und der Rest

// ****** Funktionen ******

void update_Encoder ()
{
  
  Encoder1.encode();
  Encoder1_Direction  = Encoder1.getDirection();
  Encoder1_PhaseA     = Encoder1.getA();
  Encoder1_PhaseB     = Encoder1.getB();
  Encoder1_AbsCounter = Encoder1.getAbsCounter();
  
  Encoder2.encode();
  Encoder2_Direction  = Encoder2.getDirection();
  Encoder2_RelCounter = Encoder2.getRelCounter();
  Encoder2_AbsCounter = Encoder2.getAbsCounter();

  Encoder3.encode();
  Encoder3_Direction  = Encoder3.getDirection();
  Encoder3_RelCounter = Encoder3.getRelCounter();
  Encoder3_AbsCounter = Encoder3.getAbsCounter();
}

void Ausgabe()
{
  static long Enc1_old_abs = 0;
  static long Enc2_old_abs = 0;
  static long Enc3_old_abs = 0;

  if (Encoder1_AbsCounter != Enc1_old_abs) {
    serieller_Monitor();
    Enc1_old_abs = Encoder1_AbsCounter;
  }

  if (Encoder2_AbsCounter != Enc2_old_abs) {
    serieller_Monitor();
    Enc2_old_abs = Encoder2_AbsCounter;
  }

  if (Encoder3_AbsCounter != Enc3_old_abs) {
    serieller_Monitor();
    Enc3_old_abs = Encoder3_AbsCounter;
  }
}


void serieller_Monitor ()
{  
  Ueberschriftszeile();
  Serial.print(Encoder1_Direction);       Serial.print('\t');
  Serial.print(Encoder1_PhaseA);          Serial.print('\t'); 
  Serial.print(Encoder1_PhaseB);          Serial.print('\t'); 
  Serial.print(Encoder1_AbsCounter);      Serial.print('\t'); Serial.print('\t');

  Serial.print(Encoder2_Direction);       Serial.print('\t');
  Serial.print(Encoder2_RelCounter);      Serial.print('\t');
  Serial.print(Encoder2_AbsCounter);      Serial.print('\t'); Serial.print('\t');

  Serial.print(Encoder3_Direction);       Serial.print('\t');
  Serial.print(Encoder3_RelCounter);      Serial.print('\t');
  Serial.print(Encoder3_AbsCounter);  
  Serial.println();
}


void Ueberschriftszeile ()
{
  static int counter = 33;

  counter++;

  if (counter < 30) return; // Zeilen noch nicht erreicht, Funktion abbrechen

  counter = 0;
  Serial.print(F("DIR"));   Serial.print('\t');
  Serial.print(F("Ph A"));  Serial.print('\t'); 
  Serial.print(F("Ph B"));  Serial.print('\t');
  Serial.print(F("abs"));   Serial.print('\t'); Serial.print('\t');

  Serial.print(F("DIR"));   Serial.print('\t');
  Serial.print(F("rel"));   Serial.print('\t');
  Serial.print(F("abs"));   Serial.print('\t'); Serial.print('\t');

  Serial.print(F("DIR"));   Serial.print('\t');
  Serial.print(F("rel"));   Serial.print('\t');
  Serial.print(F("abs")); 
  
  Serial.println();
}

Ich habe mir den Code nur begrenzt angesehen...

Aufgefallen ist mir:

void update_relCounter () {
static long abs_old = 0;
Das ist sehr gefährlich.
Macht es doch die Mehrfachinstanziierung, klarer die Verwendung von mehreren dieser Encoder in einem Programm, unmöglich.
Sicherheitshalber sollte man eine solche Klasse/Objekt zu einem Singleton machen.
(wenn ich das richtig gesehen habe)

Die Methode long getRelCounter() gibt es nur in den Vererbungen und ist dort unverändert gleich.

Eigentlich ist es schon richtig, eine solche Methode der Basisklasse zuzuordnen, wenn alle Ableitungen diese Methode nutzen.

Alternativ, kann man auch ein Interface ausbilden und dieses den Ableitungen zuordnen.

Beispiel:

Das Interface:

class RelCountable
{
  public:
    void update_relCounter () ;
};

Die Basisklasse:

class GreyDecoder
{
  // viel Kram
};
class GreyDecoderRelCountable : public GreyDecoder , RelCountable
{

}

Hilft dir das?

void update_relCounter () {
    static long abs_old = 0;

abs_old sollte wohl eher eine Klassenvariable sein.

Warum rufst du für den gemeinsamen Tel von encode nicht GrayEncoder::encode auf?

abs_old sollte wohl eher eine Klassenvariable sein.

Habe ich auch erst gedacht....
Halte es aber mittlerweile als Eigenschaft der Instanz für Sinnvoller.
(denke ich mal)

Gerade die OOP befreit uns von ganz vielen statischen Dingen.
Das sollte man genießen.

Warum rufst du für den gemeinsamen Tel von encode nicht GrayEncoder::encode auf?

Der Frage schließe ich mich an.

Wenn du den einzigen Unterschied deiner letzten beiden Klassen in eine Klassenvariable packst,
kannst du die beiden letzten Klassen zusammenfassen.

combie:

abs_old sollte wohl eher eine Klassenvariable sein.

Habe ich auch erst gedacht....
Halte es aber als Eigenschaft der Instanz für Sinnvoller.

Wenn du mir jetzt noch erklärst wo da der Unterschied ist, wäre ich dir dankbar.

Das Fehlen des static in meinem Satz war kein Zufall.

Hallo,

abs_old soll so lokal wie möglich sein, da sie sonst in der Klasse auch keine Verwendung hätte. Als Klassenvariable wäre sie in der Klasse gesehen global. Das möchte ich vermeiden. Ist das damit okay?

Warum die Initialisierung von abs_old = 0 gefährlich ist kann ich noch nicht sehen. Der Basiszähler, also der Absolutzähler, startet auch bei 0. In der Methode init() enc_delta = 0. Der relative Zähler benötigt einen Vergleich damit er loszählen kann. Jede Objekt/Mehrfachinstanziierung sollte doch seinen eigenen Zähler bekommen. Oder verwechsel ich etwas?

Den realtiven Zähler sollte die Basisklasse zwar nicht bekommen, aber gut, wenn es das vereinfacht, meinetwegen.

update_relCounter() muss ich noch protected machen, hierauf darf von außen nicht zugegriffen werden.
Hier kann ich eine Parameterübergabe einbauen für das unterschiedliche dividieren.

Womit ich noch nichts anfangen kann ist GrayEncoder::encode. Wie soll ich das dann erweitern?

Ich finde auch das Ermitteln der Phasen sollte eine eigene Funktion sein.

Nur die Klassen könnten dann so aussehen (ungetested)

class GrayEncoder
{
  protected:
    volatile byte const *PORT;  // verwendeter Port
    byte const PinA;            // Pin Port.Bit erste Phase
    byte const PinB;            // Pin Port.Bit zweite Phase
    int relMin;
    int relMax;
    volatile long rel_counter;  // relativer Zähler, veränderbar
    volatile long abs_counter;  // absoluter Zähler, nicht veränderbar
    int direction;              // Richtungsanzeige  +1 / -1
    int enc_delta;
    int last;
    byte Phase_A;               // erste Phase
    byte Phase_B;               // zweite Phase

    int getPhases() {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;          // convert gray to binary
      return n;
    }

  public:
    GrayEncoder (volatile byte *p_Port, byte _A, byte _B):
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      relMin(0),
      relMax(0),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }

    GrayEncoder (volatile byte *p_Port, byte _A, byte _B, int _min, int _max):
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      relMin(_min),
      relMax(_max),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }

    void init()
    {
      last = getPhases();                       // power on state
      enc_delta = 0;
    }

    void encode()
    {
      int n = getPhases();
      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (diff & 2) - 1;             // bit 1 = direction (+/-), Zähler
        abs_counter += (long) enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }
    }

    int getDirection()  {
      return direction;  // Richtungsabfrage
    }

    long getAbsCounter()  {
      return abs_counter;  // absoluten Zählwert abfragen
    }

    int getA()  {
      return Phase_A;  // Signal von Phase A
    }

    int getB()  {
      return Phase_B;  // Signal von Phase A
    }
};

class ALPS_both : public GrayEncoder
{
    long abs_old;
    byte shift;
  public:
    ALPS_both(volatile byte *p_Port, byte _A, byte _B, byte _min, byte _max, byte _shift):
      GrayEncoder (p_Port, _A, _B, _min, _max), abs_old(0), shift(_shift)
    { }

    void encode()
    {
      GrayEncoder::encode();
      long new_abs = abs_counter >> shift;

      if ( new_abs != abs_old) {
        if (new_abs > abs_old) {
          rel_counter++;
        }
        else {
          rel_counter--;
        }
        abs_old = new_abs;
      }

      if (rel_counter < relMin) {
        rel_counter = relMax;
      }
      else if (rel_counter > relMax) {
        rel_counter = relMin;
      }
    }

    long getRelCounter()   {            // relativen Zählwert abfragen
      return rel_counter;               // wird schon geviertelt +/- geändert
    }

    long getAbsCounter()   {            // absoluten Zählwert abfragen
      return abs_counter >> shift;
    }
};

GrayEncoder Encoder1 (&PINC, 0, 1);         // Phase A/B, Pin 37/36 > Port.C Bit.0 / Bit.1
ALPS_both  Encoder2 (&PINC, 0, 1, 0, 10, 2);  // Phase A/B, Pin 37/36 > Port.C Bit.0 / Bit.1 / RelMin / RelMax
ALPS_both  Encoder3 (&PING, 1, 0, 0, 10, 1); // Phase A/B, Pin 40/41 > Port.G Bit.1 / Bit.0 / RelMin / RelMax

Doc_Arduino:
abs_old soll so lokal wie möglich sein, da sie sonst in der Klasse auch keine Verwendung hätte. Als Klassenvariable wäre sie in der Klasse gesehen global. Das möchte ich vermeiden. Ist das damit okay?

Warum die Initialisierung von abs_old = 0 gefährlich ist kann ich noch nicht sehen. Der Basiszähler, also der Absolutzähler, startet auch bei 0. In der Methode init() enc_delta = 0. Der relative Zähler benötigt einen Vergleich damit er loszählen kann. Jede Objekt/Mehrfachinstanziierung sollte doch seinen eigenen Zähler bekommen. Oder verwechsel ich etwas?

Deine static Variante hatte nur einen abs_old für alle Instanzen, das geht in die Hose.

Wenn du mir jetzt noch erklärst wo da der Unterschied ist, wäre ich dir dankbar.

Vokabular .... Semantik.

Eine "Objekt Eigenschaft" oder "Instanz Eigenschaft" ist für jedes Instanz der Klasse individuell gültig.

Eine Klassen Eigenschaft ist für diese Klasse individuell gültig und damit für alle Instanzen der Klasse(auch ihrer Ableitungen) identisch.

Das Wort "Klassen Eigenschaft" oder von mir aus auch "Klassen Variable" ist recht eindeutig definiert.
Und wenn man da einfach nur das "static" weglässt, hat man einen semantischen Bock geschossen.

Vielleicht ist das etwas pingelig....
Aber wie will man zielführend kommunizieren, wenn man keine einheitliche Semantik einführt, oder/und sich daran nicht hält?

Deine static Variante hatte nur einen abs_old für alle Instanzen, das geht in die Hose.

Exakt das sind auch meine Bedenken.

combie:
Das Wort "Klassen Eigenschaft" oder von mir aus auch "Klassen Variable" ist recht eindeutig definiert.

Du wolltest mir den Unterschied zwischen Klassen Eigenschaft und Klassen Variable erklären,
jetzt ist es doch das gleiche?

combie:
Und wenn man da einfach nur das "static" weglässt, hat man einen semantischen Bock geschossen.

Wenn man bei einer Klasseneigenschaft bzw. Klassenvariable das static nicht schreibt passt das genau.

Ich habe nicht gesagt er solle aus der Funktion das static des longs entfernen.

Du wolltest mir den Unterschied zwischen Klassen Eigenschaft und Klassen Variable erklären,

Nein wollte ich nicht!

abs_old sollte wohl eher eine Klassenvariable sein.

Obwohl das auch mein erster Gedanke war, habe ich widersprochen, und gesagt das es meiner Ansicht nach eher eine Objekt oder Instanz Variable ein sollte.
Und keine Klassenvariable.

Wir sprechen offensichtlich über völlig unterschiedliche Dinge, anders kann ich mir dein Posting #10 nicht erklären.

Dass wir dabei "jetzt" auf einen grünen Zweig kommen scheint mir nicht absehbar, darum gebe ich, hier und jetzt, auf und ruhe.
Lass uns bitte nicht dieses Thema mit diesem Detail hijacken.

Hallo,

mein oben gezeigter Code funktioniert mit static. ?? Mein Gedankenproblem fängt jetzt an, dass ich bis jetzt dachte alle Objekte bzw. Instanzen bekommen ihre jeweilige eigene komplette Kopie der Klasse. Sodass eben nichts von der einen in die andere reinpfuscht. Das ist doch der Sinn der Klasse. Bin jetzt verunsichert.

Jetzt habe ich update_relCounter (byte shift) in die Basisklasse verschoben, jedoch funktioniert damit nichts mehr. Egal ob darin abs_old static ist oder nicht. Die Zählerwerte machen was sie wollen.

Den Whandall Code schau ich mir an wenn diese Probleme ausgeräumt sind. Sonst verwerfe ich meinen Code ohne den Grund zukennen.

class GrayEncoder       // Klassenname "GrayEncoder"
{
  protected:                    // private wird zu protected, nur intern zugängliche Elemente ...
    volatile byte const *PORT;  // verwendeter Port
    byte const PinA;            // Pin Port.Bit erste Phase
    byte const PinB;            // Pin Port.Bit zweite Phase
    int relMin;
    int relMax;
    volatile long rel_counter;  // relativer Zähler, veränderbar
    volatile long abs_counter;  // absoluter Zähler, nicht veränderbar
    int direction;              // Richtungsanzeige  +1 / -1
    int enc_delta;
    int last;
    byte Phase_A;               // erste Phase
    byte Phase_B;               // zweite Phase

    void update_relCounter (byte shift) {
      static long abs_old = 0;
      long new_abs = abs_counter >> shift;      // halbiert, geviertelt oder ... Steps pro Rastung
      
      if ( new_abs != abs_old) {
        if (new_abs > abs_old) {
          rel_counter++;
        }
        else {
          rel_counter--;
        }
        abs_old = new_abs;
      }

      if (rel_counter < relMin) {
        rel_counter = relMax;
      }
      else if (rel_counter > relMax) {
        rel_counter = relMin;
      }
    }
    
  public:              // von außen zugängliche Elemente ...
    GrayEncoder (volatile byte *p_Port, byte _A, byte _B):
      // Initialisierungsliste
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      relMin(0),
      relMax(0),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }

    GrayEncoder (volatile byte *p_Port, byte _A, byte _B, int _min, int _max):
      // Initialisierungsliste
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      relMin(_min),
      relMax(_max),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }
    
    void init()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;

      last = n;                       // power on state
      enc_delta = 0;
    }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;          // convert gray to binary

      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (diff & 2) - 1;             // bit 1 = direction (+/-), Zähler
        abs_counter += (long) enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }
    }

    int getDirection()  {return direction; }      // Richtungsabfrage
    
    long getRelCounter()  {return rel_counter; }  // relativen Zählwert abfragen, ist schon angepasst
                  
    long getAbsCounter()  {return abs_counter; }  // absoluten Zählwert abfragen                
   
    int getA()  { return Phase_A; }               // Signal von Phase A
    
    int getB()  { return Phase_B; }               // Signal von Phase A
};

// Klasse "ALPS_EM20B" erbt von "GrayEncoder" aber nur mit dem zweiten Konstruktor
class ALPS_EM20B : public GrayEncoder
{
  public:               // von außen zugängliche Elemente ...
    ALPS_EM20B(volatile byte *p_Port, byte _A, byte _B, byte _min, byte _max): GrayEncoder (p_Port, _A, _B, _min, _max)
    { }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;          // convert gray to binary

      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (diff & 2) - 1;             // bit 1 = direction (+/-), Zähler
        abs_counter += (long) enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }

      update_relCounter(2);
    }
        
    long getAbsCounter()   {            // absoluten Zählwert abfragen
      return abs_counter >> 2;          // 4 Steps pro Rastung
    }
};

// Klasse "ALPS_EC11" erbt von "GrayEncoder" aber nur mit dem zweiten Konstruktor
class ALPS_EC11 : public GrayEncoder
{
  public:               // von außen zugängliche Elemente ...
    ALPS_EC11(volatile byte *p_Port, byte _A, byte _B, byte _min, byte _max): GrayEncoder (p_Port, _A, _B, _min, _max)
    { }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;
      if ( Phase_B ) n ^= 1;          // convert gray to binary

      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (diff & 2) - 1;             // bit 1 = direction (+/-), Zähler
        abs_counter += (long) enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }

      update_relCounter(1);
    }
        
    long getAbsCounter()   {                // absoluten Zählwert abfragen
      return abs_counter >> 1;              // 2 Steps pro Rastung
    }
};

. Sodass eben nichts von der einen in die andere reinpfuscht. Das ist doch der Sinn der Klasse. Bin jetzt verunsichert.

Du hast aber keine Klassen oder Instanz Variable erzeugt, sondern eine lokale statische Variable in einer Funktion/Methode.
Das macht einen Unterschied.

Beweis:

class Test
{
  public:
  void testPrint()
  {
    static int t = 0;
    Serial.println(t);
    t++;
  }
};

Test a,b,c,d;


void setup() 
{
  Serial.begin(9600);      
  Serial.println("Start");
  a.testPrint();
  b.testPrint();
  c.testPrint();
  d.testPrint();

}

void loop() 
{


}

Das dürfte ein so genannter “unbeabsichtigter/unerwünschter Seiteneffekt” sein.

Hallo,

mein Eingangs gezeigter Code mit static funktioniert demnach nur, weil ich von allen "Teilklassen" nur eine Instanz erzeugt habe.

Wenn das so ist, okay. Klingt jedoch für mich komplett unlogisch warum das so ist wie es ist. Man sagt doch das jedes erzeugte Objekt komplett alles neu zugewiesen bekommt von der Klasse mit den Teilen die es benötigt. Also alles voneinander gekapselt ist. Wie kann dann eine Variable die sich innerhalb der Klasse befindet plötzlich für alle angelegten Objekte der Klasse verwendet werden? Das entzieht sich meiner Logik.

Wie kann dann eine Variable die sich innerhalb der Klasse befindet plötzlich für alle angelegten Objekte der Klasse verwendet werden?

Deine Variable ist nicht "lokal zur Klasse/Instanz", sondern "lokal zur Methode".
Hat also nichts (überhaupt nichts) mit dem Objekt/Klasse zu tun.
Außer, dass die Methode selber der Klasse/Instanz zugehörig ist.

Darum greift deine Argumentation nicht.
Eine lokale statische Variable, in einer Funktion, verhält sich identisch.

Du hast dir ein falsches Bild gemacht.
Und jetzt ist es an der Zeit dieses zu korrigieren.

Das Problem ist das Schlüsselwort "static" an sich. Das hat in C++ mindestens vier verschiedene Bedeutungen je nachdem wo man es verwendet

Lokale statische Variablen in Methoden sind aber das gleiche wie statische Klassenvariablen. Nur mit begrenztem Gültigkeitsbereich. Da darf man nicht Funktionen mit Methoden verwechseln.

Hallo,

damit ich die Korrektur meines Bildes verstehe muss ich nochmal anders formuliert fragen.
Wenn die Variable lokal zur Methode gehört und die Methode zur Klasse, dann gehört in meinen Augen auch die Variable über diesen Zwischenschritt zur Klasse. Dem ist nicht so. Warum verstehe ich nicht. Das ist mein Verständnisproblem bei der Sache.

Wenn ich auf Arbeit Anlagen repariere und tausche eine Schraube aus, dann gehört diese zu einer Baugruppe (Methode) und die Baugruppe zur Anlage (Klasse). Die gleiche Anlage daneben kann sich an der Schraube gegenüber nicht selbst bedienen oder verändern.

Mein Gedankenproblem damit verständlicher gewurden?

Wenn die Variable lokal zur Methode gehört und die Methode zur Klasse, dann gehört in meinen Augen auch die Variable über diesen Zwischenschritt zur Klasse. Dem ist nicht so. Warum verstehe ich nicht. Das ist mein Verständnisproblem bei der Sache.

Doch dem ist so!

Die Variable gehört, über die Methode, zur Klasse!

Du möchtest aber eine Variable, welche zu einer konkreten Instanz/Objekt gehört.
Du unterscheidest offensichtlich nicht zwischen Klasse und Instanz/Objekt.

Statische Variablen gehören natürlich zur Klasse, aber sie gehören zu keinem spezifischen Objekt der Klasse

Im Deutschen heißen die auch Klassenvariablen (alle Objekte der Klasse teilen sich eine Variable). Im Gegensatz zu Instanzvariablen (jede Instanz hat ihre eigene Version)