Forum: Projekte & Code Cool Kids and OOP


von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

Frei nach dem Motto "Cool Kids don't like OOP" habe ich mal ein simples 
Code-Beispiel mit klassischer Laufzeit-Polymorphie durch einen 
std::variant<>-Ansatz ersetzt (Damit es nicht so ganz abstrakt bleibt, 
hat das Beispiel eine fiktive MCU).

Ausgangscode ist eine Iteration über fiktive HW-Abstraktionen klassisch 
mit virtuellen Funktionen (bm91.cc):
1
int main() {
2
    std::array<I*, 4> p{&x1, &x2, &x3, nullptr};
3
    p[0] = &x1;
4
    while(true) {
5
        std::byte x{};
6
        for(auto&& i : p) {
7
            if (i) {
8
                i->store();
9
                x |= i->get();
10
            }
11
        }
12
        x1.set(x);
13
    }
14
}

Zweiter Versuch mit std::variant<> und Zeigern (bm90.cc):
1
int main() {
2
    std::array<std::variant<B*, A*, nullptr_t>, 4> p{&x1, &x2, &x3, nullptr};
3
    p[0] = &x4;
4
    while(true) {
5
        std::byte x{};
6
        for(auto&& i : p) {
7
            i.visit([&]<typename P>(P& v){
8
                        if constexpr(!std::is_same_v<P, nullptr_t>) {
9
                            v->store();
10
                            x |= v->get();
11
                        }
12
            });
13
        }
14
       x1.set(x);
15
    }
16
}

Dritter Versuch mit std::variant<> ohne Zeiger (bm92.cc):
1
int main() {
2
    std::array<std::variant<B, A, nullptr_t>, 4> p{x1, x2, x3, nullptr};
3
    p[0] = x4;
4
    while(true) {
5
        std::byte x{};
6
        for(auto&& i : p) {
7
            i.visit([&]<typename P>(P& v){
8
                        if constexpr(!std::is_same_v<P, nullptr_t>) {
9
                            v.store();
10
                            x |= v.get();
11
                        }
12
            });
13
        }
14
        x1.set(x);
15
    }
16
}

Für einen attiny1614 bekommt man:
1
212       4       4     220      dc bm90.elf
2
400      10       4     414     19e bm91.elf
3
190       0       4     194      c2 bm92.elf

Der generierte Code könnte besser nicht sein (bm92.s):
1
main:
2
.L2:
3
lds r24,MCU::P2::r2      ;  r2.1_47, r2
4
lds r18,MCU::P2::r2      ;  r2.1_64, r2
5
lds r25,MCU::P1::r2      ;  r2.2_76, r2
6
or r24,r18               ;  tmp48, r2.1_64
7
or r24,r25               ;  _79, r2.2_76
8
sts MCU::P1::r1,r24      ;  r1, _79
9
rjmp .L2                 ;

Der Code mit virtuellen Funktionen (bm91.s) ist wesentlich länger und 
umständlicher.

Interessant finde ich, dass der Compiler die virtuellen Funktionsaufrufe 
nicht komplett devirtualisieren kann.

Hier auch der Link in den Compiler-Explorer:

https://gcc.godbolt.org/z/5C5mMh

(etwas angepasst, damit es komplett Standard-konform ist).

Die AVR-Variante verwendet eine eigene, straight-forward realisiertes 
Klassentemplate std::variant<> als diskriminierte,rekursive Union (daher 
kein UB hier). Die Realisierung davon hänge ich auch mal an, damit die 
Idee von std::variant<> klar wird. Wobei die nicht out-of-the-box zu 
verwenden ist, da die Meta-Funktionsbibliothek fehlt, was sich aber 
recht leicht anpassen lässt).

Ist nicht nur Spielerei, denn ich habe damit schon mal ein Menu-System 
(ja tatsächlich: AVR) erfolgreich umgebaut (std::variant<> mit Zeigern): 
kleiner, schneller.

von Vincent H. (vinci)


Lesenswert?

optional & variant ♥

Mittlerweile frag ich mich wie ich eigentlich jemals ohne einem der 
beiden hab Leben können ;)

Das einzige was mich bis heute fuchst is der fehlende Referenz-Support.

von Wilhelm M. (wimalopaan)


Lesenswert?

Vincent H. schrieb:
> optional & variant ♥
>
> Mittlerweile frag ich mich wie ich eigentlich jemals ohne einem der
> beiden hab Leben können ;)

Also std::optional<> habe ich schon gaaaanz lange im Einsatz, also 
simple, eigene Variante mit C++11.

std::variant<> eher weniger. Bislang habe ich es auf µC durch einen 
komplett statischen Ansatz ersetzt, bei non-µC für diesen Einsatz eher 
selten.

von Johannes S. (Gast)


Lesenswert?

ich versuche es zu verstehen, habe variants noch nicht benutzt.
In bm91 mit einem Array kann ich aber das Array zur Laufzeit 
bauen/ändern, bei Variants nicht, richtig?
Das Menu System wäre demnach auch statisch, was aber sicher in den 
meisten Fällen auch so sein sollte.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> In bm91 mit einem Array kann ich aber das Array zur Laufzeit
> bauen/ändern, bei Variants nicht, richtig?

Vielleicht übersehen
1
    p[0] = x4;

Oder meinst Du was anderes?

von Vincent H. (vinci)


Lesenswert?

Johannes S. schrieb:
> In bm91 mit einem Array kann ich aber das Array zur Laufzeit
> bauen/ändern, bei Variants nicht, richtig?

Ein variant ist nur ein fancy union. Wie bei einem union auch kannst du 
den aktiven Typen ändern.
1
std::variant<double, char> v{42.0};  // variant enthält double
2
v = 'x';  // variant enthält char

von Theor (Gast)


Lesenswert?

Bezieht sich auf bm91.cc

Compiliert mit https://www.onlinegdb.com/online_c++_compiler
und C++17 eingestellt.

Öhm. Ist das C++20?

Fehlt da nicht ein #include <cstddef>?

Und:
Virtual und constexpr gleichzeitig?
1
inline virtual constexpr std::byte get() const = 0;

von Wilhelm M. (wimalopaan)


Lesenswert?

Theor schrieb:
> Bezieht sich auf bm91.cc
>
> Compiliert mit https://www.onlinegdb.com/online_c++_compiler
> und C++17 eingestellt.

Ich hatte extra den Link zu godbolt gepostet: da sind die Änderungen 
drin, wie oben schon geschrieben)

> Öhm. Ist das C++20?

Ja, mindestens generic lambdas mit explizitem Typparameter

>
> Fehlt da nicht ein #include <cstddef>?

s.o.

> Und:
> Virtual und constexpr gleichzeitig?

Ja, im Beispiel aber nicht wichtig.

von Johannes S. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Oder meinst Du was anderes?

nein, danke. Übersehen das es ja trotzdem ein Array ist, ein Array von 
Variants. Und der Variant darf die Typen A*, B* und nullptr annehmen, 
und das darf wie Vincent schrieb auch zur Laufzeit verändert werden. 
Solange es ein Typ ist der im Variant definiert ist, jetzt richtig?
Also eher ein enum Ersatz? Mit welchen Vorteilen?

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> und das darf wie Vincent schrieb auch zur Laufzeit verändert werden.
> Solange es ein Typ ist der im Variant definiert ist, jetzt richtig?

Ja.

Johannes S. schrieb:
> Also eher ein enum Ersatz? Mit welchen Vorteilen?

Polymorphe ohne vtable.

von Johannes S. (Gast)


Lesenswert?

ok.
Auf dem µC mit STM Target mal eine Zeile mit std::variant kompiliert, da 
explodiert aber das logfile wenn ich mit c++17/c++2a kompiliere, die HAL 
ist voll von register storage classes die nun obsolet werden sollen. Das 
ist schon ein krasser Kompatibilitätsbruch.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> ok.
> Auf dem µC mit STM Target mal eine Zeile mit std::variant kompiliert, da
> explodiert aber das logfile wenn ich mit c++17/c++2a kompiliere, die HAL
> ist voll von register storage classes die nun obsolet werden sollen. Das
> ist schon ein krasser Kompatibilitätsbruch.

Ich denke nicht: Du solltest die HAL-Header korrekt als extern "C" 
inkludieren.

von Johannes S. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Ich denke nicht: Du solltest die HAL-Header korrekt als extern "C"
> inkludieren.

sind sie. Aber der gcc ignoriert diese register Empfehlung wohl schon 
lange und ST sollte das rauswerfen. Codesize beim Kompilieren ist auch 
gleich.
Die CMSIS wurde da auch schon aktualisiert.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> Wilhelm M. schrieb:
>> Ich denke nicht: Du solltest die HAL-Header korrekt als extern "C"
>> inkludieren.
>
> sind sie. Aber der gcc ignoriert diese register Empfehlung wohl schon
> lange und ST sollte das rauswerfen. Codesize beim Kompilieren ist auch
> gleich.

Ja, mindestens 15 Jahre ;-)

Hier hast Du zwei Möglichkeiten.
1
#ifdef __cplusplus
2
# define register 
3
#endif
4
//#pragma GCC diagnostic push
5
//#pragma GCC diagnostic ignored "-Wregister"
6
extern "C" {
7
register int foo(register int);
8
}
9
//#pragma GCC diagnostic pop

> Die CMSIS wurde da auch schon aktualisiert.

Jaja, die HAL ...

von Johannes S. (Gast)


Lesenswert?

ein einfaches '-Wno-register' beim gcc oder '-Wno-deprecated-register' 
beim ARMC6 stellt den Compiler auch ruhig.
Den Quellcode selber soll ST aktualisieren, das wurde da auch schon 
gemeldet.
Es zeigt nur das die Anwender selbst bei Neuerungen im 3-Jahres Rhytmus 
nicht sofort mitziehen können. Ich finde die Features aber schon 
spannend, bringt ruhig weiter solche Beispiele.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> Es zeigt nur das die Anwender selbst bei Neuerungen im 3-Jahres Rhytmus
> nicht sofort mitziehen können.

Deprecated wurde es 2009 für C++11.

In C++17 ist die Bedeutung des keyword nun ganz verschwunden, aber noch 
reserviert.

Die Hersteller hatten 8-Jahre Zeit. Ich denke, das reicht locker (c++11, 
c++14, c++17).

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

c++14 hatte aber noch nicht gemeckert, wenn kein Wecker klingelt schläft 
man weiter :) Es wird auch immer noch als Warning, nicht als Error 
gemeldet.
Wenn viele Tests durchlaufen werden müssen und die neuen Features nicht 
benutzt werden, dann ziehen die Hersteller nicht mit.

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.