Forum: PC-Programmierung [C++] struct in class als Übergabe aus Klassen-Funktion


von Rene K. (xdraconix)


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é

von Oliver S. (oliverso)


Lesenswert?

https://en.cppreference.com/w/cpp/language/nested_types

Den Rückgabewerr deiner Funktion solltest du auch nochmal anschauen.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Rene K. schrieb:
> Kann mir das jemand erklären?

ConvBytes ist in der Klasse definiert, also in deren Namensraum:
1
DraconixClass::ConvBytes DraconixClass::From16Bit(uint16_t vari)

Den Inhalt der Funktion solltest du auch nochmal genauer betrachten.

: Bearbeitet durch User
von Rene K. (xdraconix)


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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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

von Rene K. (xdraconix)


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. :-)

von Wilhelm M. (wimalopaan)


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?

von Rene K. (xdraconix)


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
}

von Wilhelm M. (wimalopaan)


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
> ...
>
>     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...

von Klaus (feelfree)


Lesenswert?

Rene K. schrieb:
> (HVari << 8)

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

oder täusche ich mich?

von Rolf M. (rmagnus)


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).

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


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.

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.