Forum: PC-Programmierung C++ Codegüte und Operator Overloading


von Johann L. (gjlayde) Benutzerseite


Angehängte Dateien:

Lesenswert?

Hi, ich versuche mich gerade daran, in C++ Operatoren zu überladen.

Der Code sieht so aus:
1
class V
2
{
3
    public:
4
        signed char v;
5
        
6
        inline V(){};
7
};
8
9
static inline V operator+ (V a, const V b)
10
{
11
    a.v += b.v;
12
    return a;
13
}
14
15
V add (const V a, const V b)
16
{
17
    return a+b;
18
}
19
20
V set (void)
21
{
22
    V v;
23
    v.v = 123;
24
    return v;
25
}
26
27
unsigned int sizeV = sizeof (V);

Leider ist die Codegröße jenseits von Gut und Böse: Alle Operationen 
werden über den Frame bzw. this abgewickelt.

In dem C-Projekt verwende ich 8-Bit Variablen für Fixpunkt-Arithmetik, 
d.h. Q1.8, Q0.8 und 2-dimensionale Vektoren darauf. Das funktioniert 
soweit auch ganz prima und effizient, allerdings wird die C-Quelle durch 
die zig Arithmetik-Makros nicht gerade hübsch. Daher versuche ich mich 
gerade an C++, um Operatoren überladen zu können und so eine 
überschbarere Quelle zu bekommen. Zugegebenermassen hab ich immer einen 
Bogen um C++ gemach -- auf dem PC hab ich immer lieber zu was richtig 
objektorientiertem gegriffen wie Java.

Wie auch immer... Der Code, den GCC für das kleine Beispiel erzeugt, ist 
der Overkill; sowohl die Addition als auch das Setzen der Variablen 
brauchen eigentlich je nur einen Befehl. Überladen, Exceptions, 
virtuelle Methoden, Vererben brauch ich nicht und will sie hier auch 
garnicht -- ich will nur die kompakte Syntax der Operatoren-Überladung 
für + und *.

Was mir aufgefallen ist, ist daß der Konstruktor als weak angelegt wird, 
so daß es nicht geinlint werden kann vom Compiler. Wie vermeidet man 
das?

Bzw. wie ist die Quelle hinzuschreiben, daß C++ sich nicht wegen 
inakzeptabel breitem und langsamen Code raushaut und mit C mithalten 
kann?

: Verschoben durch User
von Peter (Gast)


Lesenswert?

zur codegröße kann ich nichts sagen, aber das du es dem compieler nicht 
gerade leicht machts erkenne ich.

> static inline V operator+ (V a, const V b)

damit wird bei jeder übergeben eine Kopie von a und b angelegt mit alles 
was dazu gehört (Construktor/Destruktor)

viel besser sollte es so sein

class V
{
    public:

        signed char v;

        inline V(){};
        inline V( signed char value): v(value) {};
};

static inline V operator+ (const V& a, const V& b)
{
    return V(a.v + b.v);
};

von High Performer (Gast)


Lesenswert?

>zur codegröße kann ich nichts sagen, aber das du es dem compieler nicht
>gerade leicht machts erkenne ich.

Vom OP:

>static inline V operator+ (V a, const V b)
>{
>    a.v += b.v;
>    return a;
>}

Und außerdem zeigt das Beispiel, warum operator overloading geteiltes 
Echo in der Programmiergemeinde auslöst, und mich immer wieder vom 
Wunsch abbringt, sowas auch in Java zu haben:

der OP verändert einen der Operanden der Addition, was natürlich niemand 
erwarten würde. Sobald jemand anderes dann den Code verwendet, wird er 
große Augen machen. Also doch bitte Finger weg vom Überladen von 
Operatoren, wenn man die Kontrakte nicht einhalten will.

>static inline V operator+ (const V& a, const V& b)
>{
>    return V(a.v + b.v);
>};

schon besser. ;-)

von Johann L. (gjlayde) Benutzerseite


Angehängte Dateien:

Lesenswert?

Peter schrieb:
> zur codegröße kann ich nichts sagen, aber das du es dem compieler nicht
> gerade leicht machts erkenne ich.
>
>> static inline V operator+ (V a, const V b)
>
> damit wird bei jeder übergeben eine Kopie von a und b angelegt mit alles
> was dazu gehört (Construktor/Destruktor)

sizeof(V) ist 1, also ist es nicht nötig, mehr als 1 Byte zu übergeben 
(denk ich mir so al C++ Noob). Zumindest wenn alles andere statisch 
bekannt ist, also kein virtueller Krempel etc.


> viel besser sollte es so sein [...]

Ok, machen wir es explizit:
1
class V
2
{
3
    public:
4
5
        signed char v;
6
7
        inline V(){};
8
        inline V (signed char value) : v(value) {};
9
};
10
11
static inline V operator+ (const V& a, const V& b)
12
{
13
    return V(a.v + b.v);
14
};
15
16
V add (const V a, const V b)
17
{
18
    return a+b;
19
}
20
21
V set (void)
22
{
23
    return V(128);
24
}
25
26
unsigned int sizeV = sizeof (V);

ARGL, mann bin ich blöd, ich hatte -Os vergessen. Peinlich. Jetzt 
sieht's schon besser aus :-)

von Peter (Gast)


Lesenswert?

High Performer schrieb:
> der OP verändert einen der Operanden der Addition, was natürlich niemand
> erwarten würde. Sobald jemand anderes dann den Code verwendet, wird er
> große Augen machen. Also doch bitte Finger weg vom Überladen von
> Operatoren, wenn man die Kontrakte nicht einhalten will.
das ist hier aber nicht der fall, weil er voher schon eine Kopie von dem 
Object gemacht hat.

von Rolf Magnus (Gast)


Lesenswert?

High Performer schrieb:
> Und außerdem zeigt das Beispiel, warum operator overloading geteiltes
> Echo in der Programmiergemeinde auslöst, und mich immer wieder vom
> Wunsch abbringt, sowas auch in Java zu haben:
>
> der OP verändert einen der Operanden der Addition, was natürlich niemand
> erwarten würde.

Er verändert keinen Operanden, auch wenn das ein Java-Programmierer 
nicht erwarten würde, weil der den Unterschied zwischen call-by-value 
und call-by-reference nicht kennt. ;-)

>>static inline V operator+ (const V& a, const V& b)
>>{
>>    return V(a.v + b.v);
>>};
>
> schon besser. ;-)

Nicht besser, aber zumindest auch nicht schechter.

Johann L. schrieb:
>> damit wird bei jeder übergeben eine Kopie von a und b angelegt mit
>> alles was dazu gehört (Construktor/Destruktor)
>
> sizeof(V) ist 1, also ist es nicht nötig, mehr als 1 Byte zu übergeben
> (denk ich mir so al C++ Noob). Zumindest wenn alles andere statisch
> bekannt ist, also kein virtueller Krempel etc.

Da denkst du richtig. Eine Übergabe per Referenz wäre hier sogar 
deutlich ineffizienter als per Kopie, wenn es sich nicht gerade um 
Inline-Funktionen handeln würde, wo der Compiler die Referenzierung 
einfach wegoptimieren kann. Hier sollte es also absolut keinen 
Unterschied machen.
Die meisten C++-Programmierer sind es nur gewöhnt, skalare Typen per 
Kopie und Klassen per Referenz zu übergeben, deshalb macht so eine 
Stelle im Code immer etwas hellhörig.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rolf Magnus schrieb:
>>> damit wird bei jeder übergeben eine Kopie von a und b angelegt mit
>>> alles was dazu gehört (Construktor/Destruktor)
>>
>> sizeof(V) ist 1, also ist es nicht nötig, mehr als 1 Byte zu übergeben
>> (denk ich mir so al C++ Noob). Zumindest wenn alles andere statisch
>> bekannt ist, also kein virtueller Krempel etc.
>
> Da denkst du richtig. Eine Übergabe per Referenz wäre hier sogar
> deutlich ineffizienter als per Kopie, wenn es sich nicht gerade um
> Inline-Funktionen handeln würde, wo der Compiler die Referenzierung
> einfach wegoptimieren kann. Hier sollte es also absolut keinen
> Unterschied machen.
> Die meisten C++-Programmierer sind es nur gewöhnt, skalare Typen per
> Kopie und Klassen per Referenz zu übergeben, deshalb macht so eine
> Stelle im Code immer etwas hellhörig.

Anders geht das doch nicht, oder? Jedenfalls erlaubt C++ es nicht, 
Opertoren für Skalare zu überladen.

BTW, was hat das Thema im Forum "PC-Programmierung" zu suchen?
Es geht doch darum, welchen Code avr-g++ erzeugt. avr-g++ läuft zwar auf 
einem PC, aber das interessiert hier eher weniger. Fokus ist die 
Codeerzeugung von G++ für AVR -- auch wenn's nicht explizit im Thema 
steht.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Codeerzeugung von G++ für AVR -- auch wenn's nicht explizit im
> Thema steht.
Es steht weder im Titel noch im Text das es um die Codeerzeugung von g++ 
für AVR im speziellem geht. Die Frage hat auch für PC Architekturen und 
andere Compiler Gültigkeit, weil dein Problem nicht der avr-g++ ist 
sonder C++ im allgemeinen.

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:

>> Die meisten C++-Programmierer sind es nur gewöhnt, skalare Typen per
>> Kopie und Klassen per Referenz zu übergeben, deshalb macht so eine
>> Stelle im Code immer etwas hellhörig.
>
> Anders geht das doch nicht, oder? Jedenfalls erlaubt C++ es nicht,
> Opertoren für Skalare zu überladen.

Die Frage nach dem Passing Mechanismus von Funktionsargumenten stellt 
sich ja nicht nur für Operatoren, sondern für Funktionen ganz allgemein.

Hier muss man in C++ ein wenig mehr mitdenken, als in Java oder C#

von Rolf Magnus (Gast)


Lesenswert?

Johann L. schrieb:

>> Die meisten C++-Programmierer sind es nur gewöhnt, skalare Typen per
>> Kopie und Klassen per Referenz zu übergeben, deshalb macht so eine
>> Stelle im Code immer etwas hellhörig.
>
> Anders geht das doch nicht, oder? Jedenfalls erlaubt C++ es nicht,
> Opertoren für Skalare zu überladen.

Den Zusammenhang verstehe ich jetzt nicht. Außerdem kann man durchaus 
auch Skalare als Operanden haben. Es dürfen nur nicht alle Operanden 
Skalare sein.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Läubi .. schrieb:
> Johann L. schrieb:
>> Codeerzeugung von G++ für AVR -- auch wenn's nicht explizit im
>> Thema steht.
> Es steht weder im Titel noch im Text das es um die Codeerzeugung von g++
> für AVR im speziellem geht. Die Frage hat auch für PC Architekturen und
> andere Compiler Gültigkeit, weil dein Problem nicht der avr-g++ ist
> sonder C++ im allgemeinen.

Ja stimmt, es geht nur aus dem angehängten Ausgabe von avr-g++ hervor. 
Codegröße zu diskutieren ist schon was, daß man im Hinblick auf die 
Zielarchitektur machen sollte. War ungeschickt von mit, das nicht 
nochmal zu erwähnen. Auf nem PC würden ein paar Byte mehr oder weniger 
niemanden interessieren, und ich käme auch nicht auf die Idee, mit den 
erzeugten Code anzuschauen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rolf Magnus schrieb:
> Johann L. schrieb:
>
>>> Die meisten C++-Programmierer sind es nur gewöhnt, skalare Typen per
>>> Kopie und Klassen per Referenz zu übergeben, deshalb macht so eine
>>> Stelle im Code immer etwas hellhörig.
>>
>> Anders geht das doch nicht, oder? Jedenfalls erlaubt C++ es nicht,
>> Opertoren für Skalare zu überladen.
>
> Den Zusammenhang verstehe ich jetzt nicht. Außerdem kann man durchaus
> auch Skalare als Operanden haben. Es dürfen nur nicht alle Operanden
> Skalare sein.

Das wäre dann aber der Fall, wenn man zB zwei Fixpunkt-Werte 
multiplizieren will. Da muss dann extra eine Klasse her, obwohl die nur 
einen 8-Bit Wert beinhaltet. Und die Funktionen hatte ich so 
hingeschrieben wie ich es auf nem AVR für 8-Bit Werte auch mache, 
nämlich direkt übergeben und nicht per Zeiger (oder Referenz). Für diese 
Multiplikation käme dann eine fmuls-Sequenz als Inline Asm in den 
Operator.

Leider ist C++ keine Obermenge von C (bzw. GNU-C) und es wären sehr viel 
Änderungen in der Quelle notwendig, die nichts mit diesen Operationen zu 
tun haben :-(

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:

> Das wäre dann aber der Fall, wenn man zB zwei Fixpunkt-Werte
> multiplizieren will.

Irgendetwas muss eine konstante Bedeutung haben. Wenn du eine neue 
int-int Rechnerei einführst, woher soll der Compiler dann wissen, wo 
überall diese anzuwenden ist? Soll er zb für Additionen bzw. 
Multiplikationen die im Zusammenhang mit Array Indizierung auftauchen 
ebenfalls deine neue 'Arithmetik' benutzen?

In C++ ist es nun mal das Mittel einer Klasse, sich neue Objekte mit 
neuen Eigenschaften zu bauen.

> Da muss dann extra eine Klasse her, obwohl die nur
> einen 8-Bit Wert beinhaltet.

Macht ja nichts.
Ist ausser ein wenig Schreibarbeit kein großes Ding. Dafür hast du dann 
aber auch einen Datentyp, den du beim Namen nennen kannst.

> Und die Funktionen hatte ich so
> hingeschrieben wie ich es auf nem AVR für 8-Bit Werte auch mache,
> nämlich direkt übergeben und nicht per Zeiger (oder Referenz).

Gewöhn dir für Klassenargumente gleich von vorne herein an: Übergeben 
wird im Normalfall eine Referenz oder eine const Referenz. Damit 
ermöglichst du den Compiler die besten Optimierungsmöglichkeiten. 
Überhaupt im Zusammenspiel mit inline Funktionen.

> Leider ist C++ keine Obermenge von C (bzw. GNU-C) und es wären sehr viel
> Änderungen in der Quelle notwendig, die nichts mit diesen Operationen zu
> tun haben :-(

Huch?
Neue Klasse einführen
Operatoren definieren
Verwenden

Anstelle von uint8_t oder int8_t verwendest du dann deine 
Fixedpoint-Klasse. Soo groß ist der Umstellungsaufwand dann meistens 
auch wieder nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:
> Johann L. schrieb:
>
>> Das wäre dann aber der Fall, wenn man zB zwei Fixpunkt-Werte
>> multiplizieren will.
>
> Irgendetwas muss eine konstante Bedeutung haben. Wenn du eine neue
> int-int Rechnerei einführst, woher soll der Compiler dann wissen, wo
> überall diese anzuwenden ist? Soll er zb für Additionen bzw.
> Multiplikationen die im Zusammenhang mit Array Indizierung auftauchen
> ebenfalls deine neue 'Arithmetik' benutzen?

Momentan habe ich in der C-Quelle ein
1
typedef int8_t frac8_t;
was allerdings nur dazu dient, beim Durchlesen der Quelle direkt zu 
sehen, wie so ein Ding verwendet wird. (Ok, ging auch ungarisch, mag ich 
aber net.) Immerhin geht ja sowas wie
1
typedef struct
2
{
3
  signed char v;
4
} S;
5
6
S operator+ (S a, S b)
7
{
8
  return (S) { a.v + b.v };
9
}
und da wär es naheliegend, das auch für nen normalen Typedef zu haben. 
Aber wenn es ohne Overhead möglich ist, Klassen zu verwenden, finde ich 
die Schreibarbeit, die mit Klassenbildung einhergeht, kein Drama.

>> Und die Funktionen hatte ich so
>> hingeschrieben wie ich es auf nem AVR für 8-Bit Werte auch mache,
>> nämlich direkt übergeben und nicht per Zeiger (oder Referenz).
>
> Gewöhn dir für Klassenargumente gleich von vorne herein an: Übergeben
> wird im Normalfall eine Referenz oder eine const Referenz. Damit
> ermöglichst du den Compiler die besten Optimierungsmöglichkeiten.
> Überhaupt im Zusammenspiel mit inline Funktionen.

Bei avr-g++ hab ich noch überhaupt kein Gefühl dafür, wie der tickt und 
wo seine Haken und Ösen sind. Auf nem PC hätt ich da wie gesagt keine 
Bauchschmerzen und der Code der rauskommt ist mir ziemlich wurscht. 
Zumindest kommt's da nicht auf ein paar Bytes mehr oder weniger an.

>> Leider ist C++ keine Obermenge von C (bzw. GNU-C) und es wären sehr viel
>> Änderungen in der Quelle notwendig, die nichts mit diesen Operationen zu
>> tun haben :-(
>
> Huch?
> Neue Klasse einführen
> Operatoren definieren
> Verwenden

Ich meine Sachen wie
1
#include <avr/pgmspace.h>
2
3
enum 
4
{
5
    OBJ_1,
6
    OBJ_2,
7
    OBJ_3,
8
    ...
9
};
10
11
typedef struct
12
{
13
    int8_t a;
14
    int8_t b;
15
    ...
16
} foo_t;
17
18
const foo_t foo[] PROGMEM = 
19
{
20
    [OBJ_1] = 
21
    {
22
        .a = 1,
23
        .b = 2,
24
        ...
25
    },
26
27
    [OBJ_2] = 
28
    {
29
        .a = -1,
30
        .b = 5,
31
        ...
32
    },
33
34
    [OBJ_3] = 
35
    ....
36
};

Die Designators von GNU-C sind zwar nicht notwendig, ich find einen 
Initialiser damit aber wesentlich besser lesbar als einen 
Spaghetti-Initialiser, wo man immer abzählen muss. Wenn man nen recht 
langen Initialiser hat, hat man sich schnell mal verzählt. Ausserdam ist 
es damit bei eine Typänderung wesentlich einfacher, die Initialiser 
konstant zu halten.

Wenn ich's recht sehe, gibt es sowas wie Designators in C++ nicht?

Ausserdem kann man offenbar keine Objekte ins Flash legen und von dort 
aus einlesen. Gibt eine Warnung von avr-g++ auch wenn der erzeugt Code 
ok ist. Ist wohl ein Fall der im AVR-Backend von gcc nicht abgehandelt 
wird. Die Warnung zum Attribut pgmspace kommt ja von dort.

D.h. wenn die Quelle ohne Warnungen übersetzt werden soll, dann muss man 
jede Klasse 2x anlegen: einmal als Klasse und einmal als Struktur, wobei 
letzte ins Flash gelegt werden kann um die Klasse daraus zu 
deserialisieren.

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:

> und da wär es naheliegend, das auch für nen normalen Typedef zu haben.

Ein Typedef ist nur ein Alias. Also ein anderer Name. Nach
1
typedef int my_int;
ist my_int in allen Belangen völlig gleichwertig mit einem int.

Leider.


>> Gewöhn dir für Klassenargumente gleich von vorne herein an: Übergeben
>> wird im Normalfall eine Referenz oder eine const Referenz. Damit
>> ermöglichst du den Compiler die besten Optimierungsmöglichkeiten.
>> Überhaupt im Zusammenspiel mit inline Funktionen.
>
> Bei avr-g++ hab ich noch überhaupt kein Gefühl dafür, wie der tickt und
> wo seine Haken und Ösen sind.

Das hat überhaupt nichts mit dem gcc zu tun, sondern mit C++ an sich. 
Eine Referenz ist ganz einfach nur "ein anderer Name für ein an sonsten 
existierendes Objekt".
Damit darf der Compiler überall dort, wo er eine Referenz hat auch das 
originale Objekt benutzen, wenn er an es rankommt.

> Auf nem PC hätt ich da wie gesagt keine
> Bauchschmerzen

genau so sehen dann auch viele C++ Programme aus und alle Welt schreit: 
Memory Management in C++ ist so schwierig, wir brauchen unbedingt 
Reference-counted garbage collected Klassen.


> Wenn ich's recht sehe, gibt es sowas wie Designators in C++ nicht?

Noch nicht.

von Klaus W. (mfgkw)


Lesenswert?

Karl heinz Buchegger schrieb:
> Damit darf der Compiler überall dort, wo er eine Referenz hat auch das
> originale Objekt benutzen, wenn er an es rankommt.

Oder genauer: er wird über die Referenz immer das originale Objekt
benutzen; es gibt da keine Wahlfreiheit.

von Rolf Magnus (Gast)


Lesenswert?

Johann L. schrieb:

> Momentan habe ich in der C-Quelle ein
> typedef int8_t frac8_t;
>
> was allerdings nur dazu dient, beim Durchlesen der Quelle direkt zu
> sehen, wie so ein Ding verwendet wird.

Mehr geht mit einem Typedef auch nicht.

> Immerhin geht ja sowas wie
>
> typedef struct
> {
>   signed char v;
> } S;
>
> S operator+ (S a, S b)
> {
>   return (S) { a.v + b.v };
> }
>
> und da wär es naheliegend, das auch für nen normalen Typedef zu haben.

Das ist auch ein "nomaler Typedef", den du halt nur mit einer 
Klassendefinition verwurstet hast.
Du definierst hier  auf etwas umständliche Art eine Struktur mit Namen 
S, indem du erst eine namenlose Struktur erzeugst und dann über Typedef 
ihr den "alternativen" Namen S gibst und diese beiden Aktionen zu einer 
kombinierst. Es ist also eine verkürzte Schreibeweise von ungefähr dem:
1
struct Namenlos
2
{
3
   signed char v;
4
};
5
6
typedef Namenlos S;

Aber den Typedef könnte man sich auch sparen und stattdessen gleich 
schreiben:
1
struct S
2
{
3
   signed char v;
4
};

Der Effekt wäre derselbe.
Der Typedef verhält sich hier also auch nicht anders als beim int8_t.
Der Unterschied, weshalb du hier einen Operator definieren kannst, hat 
also nichts damit zu tun, daß der Typedef nicht "normal" wäre, sondern 
damit, daß der im einen Fall auf einen skalaren Typ verweist, im anderen 
auf eine Klasse.

> Aber wenn es ohne Overhead möglich ist, Klassen zu verwenden, finde ich
> die Schreibarbeit, die mit Klassenbildung einhergeht, kein Drama.

Das ist größtenteils durchaus möglich.

> Die Designators von GNU-C sind zwar nicht notwendig, ich find einen
> Initialiser damit aber wesentlich besser lesbar als einen
> Spaghetti-Initialiser, wo man immer abzählen muss. Wenn man nen recht
> langen Initialiser hat, hat man sich schnell mal verzählt.

Zur Not könnte man das auch mit Kommentaren machen.

> Ausserdam ist es damit bei eine Typänderung wesentlich einfacher, die
> Initialiser konstant zu halten.

Warum?

> Wenn ich's recht sehe, gibt es sowas wie Designators in C++ nicht?

Nein.

> Ausserdem kann man offenbar keine Objekte ins Flash legen und von dort
> aus einlesen. Gibt eine Warnung von avr-g++ auch wenn der erzeugt Code
> ok ist.

Was denn für eine?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rolf Magnus schrieb:
> Es ist also eine verkürzte Schreibeweise von ungefähr dem:
>
>
1
> struct Namenlos
2
> {
3
>    signed char v;
4
> };
5
> 
6
> typedef Namenlos S;
7
>
1
 typedef struct Namenlos S;
2
3
> Aber den Typedef könnte man sich auch sparen und stattdessen gleich
4
> schreiben:
5
> 
6
> [C]
7
> struct S
8
> {
9
>    signed char v;
10
> };
11
>
>
> Der Effekt wäre derselbe.

Nicht, wenn man schreibfaul ist :-)

>> Die Designators von GNU-C sind zwar nicht notwendig, ich find einen
>> Initialiser damit aber wesentlich besser lesbar als einen
>> Spaghetti-Initialiser, wo man immer abzählen muss. Wenn man nen recht
>> langen Initialiser hat, hat man sich schnell mal verzählt.
>
> Zur Not könnte man das auch mit Kommentaren machen.
>
>> Ausserdam ist es damit bei eine Typänderung wesentlich einfacher, die
>> Initialiser konstant zu halten.
>
> Warum?

Typo. Soll heissen

>> Ausserdam ist es damit bei eine Typänderung wesentlich einfacher, die
>> Initialiser konsistent zu halten.
>>             ^^^^^^^^^^

D.h. wenn in der Entwicklungsphase Felder hinzukommen / verschwinden 
etc. Ein Kommentar ist ja kein Teil der Semantik, ein Designator schon.

>> Wenn ich's recht sehe, gibt es sowas wie Designators in C++ nicht?
>
> Nein.
>
>> Ausserdem kann man offenbar keine Objekte ins Flash legen und von dort
>> aus einlesen. Gibt eine Warnung von avr-g++ auch wenn der erzeugt Code
>> ok ist.
>
> Was denn für eine?
1
class V
2
{
3
    public:
4
5
        signed char v;
6
7
        inline V(){};
8
        inline V (signed char value) : v(value) {};
9
};
10
11
#include <avr/pgmspace.h>
12
13
const uint8_t q[] PROGMEM = 
14
{
15
    0x12, 0x11
16
};
17
18
const V w[] PROGMEM = 
19
{
20
    V(0x12)
21
};
22
23
V read (void)
24
{
25
    return V (pgm_read_byte (&q));
26
}
1
warning: only initialized variables can be placed into program memory area
2
warning: only initialized variables can be placed into program memory area
3
error: q causes a section type conflict

Und auch ohne w gibt's ne Warnung für q, wennglaich auch keinen 
irreführenden Fehler.

von Rolf Magnus (Gast)


Lesenswert?

Johann L. schrieb:
>> Der Effekt wäre derselbe.
>
> Nicht, wenn man schreibfaul ist :-)

Stimmt. Das 'typedef' muß man bei deiner Variante zusätzlich tippen. Das 
ist dann aber auch der einzige Unterschied.

> warning: only initialized variables can be placed into program memory area

Ah. Ich schätze, das kommt daher, daß es sich um dynamische 
Initialisierung handelt. Während q nämlich direkt mit einer Konstante 
initialisiert wird, wird das bei w durch einen Funktionsaufruf 
(Konstruktor) gemacht, und (auch wenn's komisch klingt) das würde 
bedeuten, daß der Konstruktor den Flash beschreiben können müßte. In 
diesem Fall könnte das zwar auch wegoptimiert werden, weil der 
Initialisierer ja auch schon im Flash steht und vom Konstruktor nur in 
die Membervariable kopiert wird, aber im allgemeinen Fall geht das eben 
nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rolf Magnus schrieb:
> Johann L. schrieb:
>>> Der Effekt wäre derselbe.
>>
>> Nicht, wenn man schreibfaul ist :-)
>
> Stimmt. Das 'typedef' muß man bei deiner Variante zusätzlich tippen. Das
> ist dann aber auch der einzige Unterschied.

Nö. Ansonsten muss bei jeder Definition/Deklaration/Cast ein 
struct/union hinzu.

>> warning: only initialized variables can be placed into program memory area
>
> Ah. Ich schätze, das kommt daher, daß es sich um dynamische
> Initialisierung handelt. Während q nämlich direkt mit einer Konstante
> initialisiert wird, wird das bei w durch einen Funktionsaufruf

Die Warnung kommt auch bei
1
class V
2
{
3
    public:
4
        signed char v;
5
        inline V(){};
6
        inline V (signed char value) : v(value) {};
7
};
8
9
#include <avr/pgmspace.h>
10
11
const uint8_t q[] PROGMEM = 
12
{
13
    0x12, 0x11
14
};
15
16
V read (void)
17
{
18
    return V (pgm_read_byte (&q));
19
}

> (Konstruktor) gemacht, und (auch wenn's komisch klingt) das würde
> bedeuten, daß der Konstruktor den Flash beschreiben können müßte. In
> diesem Fall könnte das zwar auch wegoptimiert werden, weil der
> Initialisierer ja auch schon im Flash steht und vom Konstruktor nur in
> die Membervariable kopiert wird, aber im allgemeinen Fall geht das eben
> nicht.

Mit dem Initializer für w (der V() verwendet) kommt ja auch wie erwartet 
ein Fehler, aber der beschwert sich über q !

von Andreas F. (aferber)


Lesenswert?

Johann L. schrieb:
>> Stimmt. Das 'typedef' muß man bei deiner Variante zusätzlich tippen. Das
>> ist dann aber auch der einzige Unterschied.
> Nö. Ansonsten muss bei jeder Definition/Deklaration/Cast ein
> struct/union hinzu.

In C++? Nein. "struct foo;" deklariert in C++ einen Klassen-Typ namens 
"foo", der ohne "struct" davor verwendet werden kann. Dasselbe gilt auch 
für "union". Im Gegenteil, es ist eine explizite Ausnahme im Standard 
nötig, damit ein "typedef struct foo foo;" zu keinem Fehler führt (die 
Ausnahme ist wohl im wesentlichen der Verwendbarkeit von C-Headern 
geschuldet). Ein "struct foo; typedef int foo;" liefert in C++ einen 
Fehler, während es in C erlaubt wäre.

Andreas

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:
> Rolf Magnus schrieb:
>> Johann L. schrieb:
>>>> Der Effekt wäre derselbe.
>>>
>>> Nicht, wenn man schreibfaul ist :-)
>>
>> Stimmt. Das 'typedef' muß man bei deiner Variante zusätzlich tippen. Das
>> ist dann aber auch der einzige Unterschied.
>
> Nö. Ansonsten muss bei jeder Definition/Deklaration/Cast ein
> struct/union hinzu.

Ähm. nein.

In C: ja
In C++: nein

von Andreas F. (aferber)


Lesenswert?

Johann L. schrieb:
>> Ah. Ich schätze, das kommt daher, daß es sich um dynamische
>> Initialisierung handelt. Während q nämlich direkt mit einer Konstante
>> initialisiert wird, wird das bei w durch einen Funktionsaufruf
> Die Warnung kommt auch bei

Das dürfte BTW eine Instanz dieses GCC-Bugs sein:

http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=57011
Beitrag "avr-gcc, C++ und PROGMEM"

Andreas

von High Performer (Gast)


Lesenswert?

@Rolf Magnus:

>Er verändert keinen Operanden, auch wenn das ein Java-Programmierer
>nicht erwarten würde, weil der den Unterschied zwischen call-by-value
>und call-by-reference nicht kennt. ;-)

Ich bin mit C++ groß geworden und erst später in Java eingestiegen. ;-) 
Allerdings sehe ich in:

>static inline V operator+ (V a, const V b)
>{
>    a.v += b.v;
>    return a;
>}

keine Referenz, und außerdem bleibe ich dabei: die Methode verändert a, 
was laut Kontrakt des Additionsoperators nicht sein sollte. Außerdem 
liefert er dann (das veränderte) a als Ergebnis, was ebenfalls nicht 
sein sollte.
Oder habe ich irgend etwas Wichtiges übersehen?

von Rolf Magnus (Gast)


Lesenswert?

High Performer schrieb:
> @Rolf Magnus:
>
>>Er verändert keinen Operanden, auch wenn das ein Java-Programmierer
>>nicht erwarten würde, weil der den Unterschied zwischen call-by-value
>>und call-by-reference nicht kennt. ;-)
>
> Ich bin mit C++ groß geworden und erst später in Java eingestiegen. ;-)
> Allerdings sehe ich in:
>
>>static inline V operator+ (V a, const V b)
>>{
>>    a.v += b.v;
>>    return a;
>>}
>
> keine Referenz,

Ja, eben. Genau deshalb kann der Operator auch den Operanden gar nicht 
verändern. Er wurde "by value" übergeben und nicht per Referenz. Der 
Operator verändert lediglich eine lokale Kopie. Man könnte das natürlich 
auch selber machen, indem man den Parameter als Referenz definiert und 
dann innerhalb der Funktion selbst eine Kopie anlegt, aber das ist 
umständlicher und hat keinen Vorteil.

> und außerdem bleibe ich dabei: die Methode verändert a,

Sie verändert a, aber a ist nicht der übergebene Operand, sondern eine 
Kopie davon.

> was laut Kontrakt des Additionsoperators nicht sein sollte.

Wenn er nicht veränderbar sein soll, mußt du ihn als const definieren. 
Aber außerhalb der Funktion hätte das keinerlei Auswirkung.

von High Performer (Gast)


Lesenswert?

@Rolf:

Du hast natürlich vollkommen Recht! Ich sah mal wieder den Wald vor 
lauter Bäumen nicht. Hänge gerade wohl doch ein wenig zu sehr in Java 
drin. ;-)

Mea culpa!

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.