[C++] struct in class als Übergabe aus Klassen-Funktion

OP #7333314
Lesenswert?

Ich bin recht neu in CPP und probiere mich gerade ein wenig in Klassen. Jedoch fällt mir da so das eine oder andere etwas schwer. Momentan tue ich mich schwer mit einer struct, welche ich in einer Klasse definiere. In dieser Klasse möchte ich dieses Struct als Rückgabewert haben. Das funktioniert jedoch nicht so wie ich mir das denke. Woran könnte das liegen und vor allem warum?

1
// Header File:
2
class DraconixClass{
3

4
  public:
5
    DraconixClass(void);
6
    
7
    struct ConvBytes {
8
      uint8_t LByte;
9
      uint8_t HByte;
10
    };
11
    
12
    void begin(void);
13
    void handle(void);
14

15
  protected:
16
    ConvBytes From16Bit(uint16_t vari);
17
    uint16_t To16Bit(ConvBytes vari);
18
};
19

20
//CPP File:
21
ConvBytes DraconixClass::From16Bit(uint16_t vari)
22
{
23
  ConvBytes ByteArray;
24
  
25
  ConvBytes.LByte = vari & 0xff;
26
  ConvBytes.HByte = (vari >> 8) & 0xff;
27
  
28
  return ConvBytes;
29
}

Als Fehler gibt er mir zurück:

1
draconix.cpp:55:1: error: 'ConvBytes' does not name a type
2
   55 | ConvBytes DraconixClass::From16Bit(uint16_t vari)
3
      | ^~~~~~~~~

Kann mir das jemand erklären?

Liebe Grüße René

OP #7333331
Lesenswert?

Oh vielen Dank euch beiden - ja nested types lese ich mir mal durch. :-D

p.s.: Jaaaaaa... da schäme ich mich gerade ein wenig für den Variablen Namen - bzw. das falsche schreiben. x-D p.p.s.: So weit ist der Compiler garnicht vorgedrungen, zumindest hatte er es mir noch nicht als Fehler angekreidet.

OP #7333761
Lesenswert?

Wilhelm M. schrieb:

Auf den Inhalt der einen Funktion wurde ja schon hingewiesen ... der Rest der Klasse benötigt auch noch Aufmerksamkeit!

Ja ich hatte da nur noch "ConvBytes" im Kopf. :-D

Im übrigen funktioniert es nun tadellos.

Was mich nur so ein wenig irritiert hat, bzw. ich nur so halb nachvollziehen kann. Wenn ich die Funktion ja schon innerhalb der Klasse definiere und starte - das ich dann separat noch die Übernahme und Rückgabedefinitionen spearat der Klasse zuweisen muss.

Was aber im zweiten Blick wieder durchaus Sinn macht. Vielleicht will man ja auch Variablen Definitionen einer anderen Klasse nutzen...

Aber nun, ich danke vielmals. :-)

#7333763
Lesenswert?

Rene K. schrieb:

Was mich nur so ein wenig irritiert hat, bzw. ich nur so halb nachvollziehen kann. Wenn ich die Funktion ja schon innerhalb der Klasse definiere und starte - das ich dann separat noch die Übernahme und Rückgabedefinitionen spearat der Klasse zuweisen muss.

Du sprichst wirr ;-) Es wäre besser, wenn Du versuchst die richtigen Fachtermini zu verwenden.

Wie hast Du denn Deinen Quelltext nun verändert? Und was ist daran unklar?

OP #7333784
Lesenswert?

Wilhelm M. schrieb:

richtigen Fachtermini

Ich kenne scheinbar die richtigen Fachtermini nicht. :-)

Wilhelm M. schrieb:

Wie hast Du denn Deinen Quelltext nun verändert? Und was ist daran unklar?

Momentan sieht es so aus:

1
//Header
2

3
class DraconixClass{
4

5
  public:
6
    DraconixClass(void);
7

8
    struct ConvBytes {
9
      uint8_t LByte;
10
      uint8_t HByte;
11
    };
12
    
13
    void begin(void);
14
    void handle(void);
15
    
16
    ConvBytes From16Bit(uint16_t vari);
17
    uint16_t  To16Bit(ConvBytes vari);
18
    uint16_t  To16Bit(char LVari, char HVari);
19
};
20

21
//CPP File
22

23
DraconixClass::ConvBytes DraconixClass::From16Bit(uint16_t vari)
24
{
25
  ConvBytes ByteArray;
26
  
27
  ByteArray.LByte = vari & 0xff;
28
  ByteArray.HByte = (vari >> 8) & 0xff;
29
  
30
  return ByteArray;
31
}
32

33
uint16_t DraconixClass::To16Bit(DraconixClass::ConvBytes vari)
34
{
35
  uint16_t TempVar = (vari.HByte << 8) | vari.LByte;
36
  return TempVar;
37
}
38

39
uint16_t DraconixClass::To16Bit(char LVari, char HVari)
40
{
41
  uint16_t TempVar = (HVari << 8) | LVari;
42
  return TempVar;
43
}

So mich hat es nur gewundert das ich die Klasse an den Markierten stellen nochmal anweisen muss, obwohl ich die den Return der Funktion in der Klasse schon definiert habe.

1
//in HEADER
2

3
...
4
        
5
    ConvBytes From16Bit(uint16_t vari);
6
//      ^-- hier definiert
7
    uint16_t  To16Bit(ConvBytes vari);
8
//                        ^-- hier defniert
9
...
10

11
// CPP Datei:
12

13
..
14
//  Hier separat mit Klasse definiert obwohl die Funktion aus der Klasse ist  
15
DraconixClass::ConvBytes DraconixClass::From16Bit(uint16_t vari)
16
{
17
//..
18
}
#7333834
Lesenswert?

Rene K. schrieb:

Wilhelm M. schrieb:

richtigen Fachtermini

Ich kenne scheinbar die richtigen Fachtermini nicht. :-)

Ja. Hast Du wenigstens ein C++-Buch? Welches?

So mich hat es nur gewundert das ich die Klasse an den Markierten stellen nochmal anweisen muss, obwohl ich die den Return der Funktion in der Klasse schon definiert habe. //in HEADER ...

1
ConvBytes From16Bit(uint16_t vari);

Die Zeile stammt aus der Definition der Klasse und Du deklarierst damit die Elementfunktion From16Bit(...). Eine Elementfunktion ist wie der name sagt ein Element der Klasse. Deswegen ist der Name der Funktion auch <Klassenname>::<Elementfunktionsname>(...).

DraconixClass::ConvBytes DraconixClass::From16Bit(uint16_t vari) { }

Diese Zeile steht sozusagen "auf der grünen Wiese", also außerhalb jeder Klasse (oder Namensraum). Damit muss Du den vollständigen Funktionsnamen angeben.

Der Rückgabetyp ist ein geschachtelter Typ. Hier gilt ähnliches: der vollständige Name ist <OuterClass>::<nestedClass>. Da Du eben auf der grünen Wiese bist, musst Du auch hier den vollständig qualifizierten Namen angeben.

BTW: Deine Funktionen sind "echte" Funktionen (im mathematischen Sinn), d.h. sie haben keinen internen Zustand bzw. hängen auch nicht vom Objektzustand ab. Daher kannst / solltest Du sie auch als freie Funktionen schreiben (oder als static-Elementfunktionen)

Wenn es doch non-static Elementfunktionen sein sollen aus irgendwelchen Gründen, dann sollten sie als Beobachterfunktionen d.h. const (read-only) qualifiziert werden.

Leere Parameterlisten in C++ sind einfach f() und nicht f(void) wie in C.

Primitive Datentypen sollten immer initialisiert werden, am besten durch in-class-initializer.

1
class DraconixClass{
2
  public:
3
    DraconixClass(); // Was soll der std-ctor machen?
4

5
    struct ConvBytes {
6
      uint8_t LByte{0};
7
      uint8_t HByte{0};
8
    };
9
    
10
    void begin(); // muessen wohl Mutatoren sein, daher non-const
11
    void handle(); // Welche Datenelemente sollen die ändern?
12
    
13
    // ggf. als freie Funktionen bzw. static
14
    ConvBytes From16Bit(uint16_t vari) const;
15
    uint16_t  To16Bit(ConvBytes vari) const;
16
    uint16_t  To16Bit(uint8_t LVari, uint8_t HVari) const;
17
};

und beispielhaft

1
DraconixClass::ConvBytes DraconixClass::From16Bit(const uint16_t vari) {
2
  return {vari & 0xff, (vari >> 8) & 0xff}; // Aggregat-Initialisierung
3
}

Das waren jetzt nur ein paar Hinweise und ggf- Tips zum Weiterlesen...

#7334459
Lesenswert?

Wilhelm M. schrieb:

und beispielhaft DraconixClass::ConvBytes DraconixClass::From16Bit(const uint16_t vari) { return {vari & 0xff, (vari >> 8) & 0xff}; // Aggregat-Initialisierung }

Da fehlt ein const.

Klaus schrieb:

Rene K. schrieb:

(HVari << 8)

war da nicht was wie "shift-operationen von signed typen -> implementation-defined?"

Nicht ganz. Bei einem a << b mit vorzeichenbehafteten a ist alles ok, wenn a positiv ist und das Ergebnis von 2 hoch b in den Zieltyp passt. Wenn nicht, ist es allerdings nicht nur implementation-defined sondern undefined behavior. Was allerdings implementation-defined ist, ist ob char signed ist oder nicht. Zu beachten ist noch die integer-promotion, die HVari erst mal auf int erweitert, bevor geshiftet wird. Insgesamt heißt das, dass es tatsächlich in zwei Fällen zu undefiniertem Verhalten kommen kann, nämlich wenn HVari negativ ist (auf Compilern mit vorzeichenbehaftetem char) oder wenn das Ergebnis des Shifts nicht mehr in int passt (bei einem Compiler mit vorzeichenlosem char und einem 16 Bit breiten int, wenn HVari > 127 ist).

#7334701
Lesenswert?

Rolf M. schrieb:

Wilhelm M. schrieb:

und beispielhaft DraconixClass::ConvBytes DraconixClass::From16Bit(const uint16_t vari) { return {vari & 0xff, (vari >> 8) & 0xff}; // Aggregat-Initialisierung }

Da fehlt ein const.

Das stimmt (ich hatte leider auf eine Compilation verzichtet).

Klaus schrieb:

Rene K. schrieb:

(HVari << 8)

war da nicht was wie "shift-operationen von signed typen -> implementation-defined?"

Nicht ganz. Bei einem a << b mit vorzeichenbehafteten a ist alles ok, wenn a positiv ist und das Ergebnis von 2 hoch b in den Zieltyp passt. Wenn nicht, ist es allerdings nicht nur implementation-defined sondern undefined behavior. Was allerdings implementation-defined ist, ist ob char signed ist oder nicht. Zu beachten ist noch die integer-promotion, die HVari erst mal auf int erweitert, bevor geshiftet wird.

Deswegen hatte ich die Signatur der Funktionen auch gleich angepasst.

Insgesamt heißt das, dass es tatsächlich in zwei Fällen zu undefiniertem Verhalten kommen kann, nämlich wenn HVari negativ ist (auf Compilern mit vorzeichenbehaftetem char) oder wenn das Ergebnis des Shifts nicht mehr in int passt (bei einem Compiler mit vorzeichenlosem char und einem 16 Bit breiten int, wenn HVari > 127 ist).

Ab C++20 gibt es nur noch einen Fall von UB, nämlich wenn der rechte Operand größer ist als die Bitbreite des linken Operanden (nach ggf. Promotion) oder negativ ist.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren