Forum: PC-Programmierung C++ : Mit for-Schleife über Objekte verschiedener Klassen iterieren


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Guten Abend,

ich bin noch am Anfang meiner C++-Gehversuche, aber beim Sichten aller 
Array-ähnlichen Konstrukte, die ich in meinem Buch und auf 
https://en.cppreference.com bislang gesichtet habe, scheint es nur 
"arrays" gleicher Klassen zu geben.

Angenommen, ich hätte eine Gruppe von abgeleiteten Klassen einer 
Basisklasse, die alle die gleiche Methode (z.B. disp()) besitzen - gibt 
es eine Möglichkeit, diese in eine Art array zu packen und mit einer 
for-Schleife darüberzuiterieren?

Kann mir jemand das passende Stichwort geben?

:
von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Pointer (bzw. Iteratoren) und virtuelle Funktionen. Das ist DIE 
Grundstruktur in C++.

Oliver

: Bearbeitet durch User
von Gähn (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Du kannst zB Objekte von Typ LKW, Motorrad, Fahrrad und PKW haben, die 
alle von "Fahrzeug" abgeleitet sind.
Die kannst du dann in ein Array von Fahrzeugen speichern und kannst die 
Methode fahren() aufrufen, falls diese Methode in Fahrzeug definiert 
ist.

von Walter T. (nicolas)


Bewertung
1 lesenswert
nicht lesenswert
Gähn schrieb:
> Die kannst du dann in ein Array von Fahrzeugen

Wenn ich die Objekte aus "Motorrad", "Auto" etc. in ein Array mit 
"Fahrzeug" packe - gehen nicht die zusätzlichen Attribute von "Motorrad" 
verloren? Wird dann nicht die Methode Auto::fahren() anstelle 
Motorrad::fahren() aufgerufen (oder geht schief, wenn Auto::fahren() nur 
eine virtuelle Funktion ist?)

: Bearbeitet durch User
von Vincent H. (vinci)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Gähn schrieb:
>> Die kannst du dann in ein Array von Fahrzeugen
>
> Wenn ich die Objekte aus "Motorrad", "Auto" etc. in ein Array mit
> "Fahrzeug" packe - gehen nicht die zusätzlichen Attribute von "Motorrad"
> verloren? Wird dann nicht die Methode Auto::fahren() anstelle
> Motorrad::fahren() aufgerufen (oder geht schief, wenn Auto::fahren() mir
> eine virtuelle Funktion ist?

Richtig, das ist mal wieder absoluter Blödsinn was hier geschrieben 
wird.
1
#include <array>
2
#include <cstdio>
3
#include <memory>
4
5
struct Fahrzeug {
6
  virtual void foo() const { puts(__PRETTY_FUNCTION__); }
7
};
8
9
struct Auto : Fahrzeug {
10
  void foo() const final { puts(__PRETTY_FUNCTION__); }
11
};
12
13
struct LKW : Fahrzeug {
14
  void foo() const final { puts(__PRETTY_FUNCTION__); }
15
};
16
17
int main() {
18
  std::array<std::unique_ptr<Fahrzeug>, 2u> fahrzeuge{};
19
  fahrzeuge[0u] = std::make_unique<Auto>();
20
  fahrzeuge[1u] = std::make_unique<LKW>();
21
  fahrzeuge[0u]->foo(); // Auto::foo
22
  fahrzeuge[1u]->foo(); // LKW::foo
23
}

: Bearbeitet durch User
von Tom K. (ez81)


Bewertung
0 lesenswert
nicht lesenswert
Das funktioniert natürlich auch mit der gewünschten for-Schleife:
1
    for(const auto& f: fahrzeuge)
2
        f->foo();

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Danke, das heisst, prinzipiell ist es möglich. Dann habe ich mit 
unique_ptr und "iterator" ja erst einmal wieder den passenden Lesestoff.

Um ehrlich zu sein, ist die Aussicht, über eine Liste unterschiedlicher 
Objekte iterieren zu können, überhaupt erst der Grund, mit C++ 
angefangen zu haben.

Könnt ihr mir noch verraten, ob es prinzipiell möglich ist, diese Liste 
komplett im Flash eines Mikrocontrollers zu halten? (Auf Anhieb sehe ich 
da kein ernsthaftes Hindernis, aber ich kenne die Möglichkeiten der 
Sprache noch nicht.)

: Bearbeitet durch User
von c-hater (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Walter T. schrieb:

> Um ehrlich zu sein, ist die Aussicht, über eine Liste unterschiedlicher
> Objekte iterieren zu können, überhaupt erst der Grund, mit C++
> angefangen zu haben.

1) das wäre auch in jeder anderen OOP-Sprache möglich. Du kannst immer 
Objekte iterieren, die eine gemeinsame Basisklasse haben und die in 
einem Konstrukt stecken, über das man iterieren kann.

2) man kann das auch in Nicht-OOP-Sprachen realisieren. Man muss halt 
nur das OOP-Feature "Polymorphie" mit den Mitteln dieser Sprache 
abbilden. Für C würde das beispielweise bedeuten: Iteriert wird z.B. 
über ein Array von Strukturen und diese Strukturen enthalten 
Funktionszeiger für all den Scheiß, der bei C++ als virtuelle Methoden 
auftaucht.

Der Witz ist: viel weniger schick als in C++ wird es auch in C nicht. 
Jedenfalls sobald man für ein spezielles Objekt im Zuge der Iteration 
dann doch das braucht, was nicht schon in der gemeinsamen Basisklasse 
als virtuelle Methode existiert. Dann sieht das in C++ schon wieder fast 
genauso aufwendig aus, wie in C...

von Vincent H. (vinci)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Danke, das heisst, prinzipiell ist es möglich. Dann habe ich mit
> unique_ptr und "iterator" ja erst einmal wieder den passenden Lesestoff.
>
> Um ehrlich zu sein, ist die Aussicht, über eine Liste unterschiedlicher
> Objekte iterieren zu können, überhaupt erst der Grund, mit C++
> angefangen zu haben.
>
> Könnt ihr mir noch verraten, ob es prinzipiell möglich ist, diese Liste
> komplett im Flash eines Mikrocontrollers zu halten? (Auf Anhieb sehe ich
> da kein ernsthaftes Hindernis, aber ich kenne die Möglichkeiten der
> Sprache noch nicht.)

Code landet "immer" in der .text Section eines Mikrocontrollers und 
damit im Flash. Egal von welcher Programmiersprache der kommt.

Ich glaub du würdest gerne andere Fragen stellen, bist aber noch nicht 
weit genug um diese formulieren zu können. Du musst glaub ich erst 
einmal verstehen was Polymorphie im Prinzip ist, welche Arten es davon 
gibt und wie diese in C++ repräsentiert werden können.

von c-hater (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Walter T. schrieb:

> Könnt ihr mir noch verraten, ob es prinzipiell möglich ist, diese Liste
> komplett im Flash eines Mikrocontrollers zu halten?

Die Liste der Obejkte ist nur eine Liste von Zeigern (auch wenn die 
möglicherweise Referenzen heißen: im Kern sind's doch nur dämliche 
Zeiger).

Das Problem ist eher, dass die Objekte selber Speicherplatz im RAM 
benötigen. Und virtuelle Methoden tragen hier sogar ziemlich dick auf...

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
c-hater schrieb:
> 2) man kann das auch in Nicht-OOP-Sprachen realisieren. Man muss halt
> nur das OOP-Feature "Polymorphie" mit den Mitteln dieser Sprache
> abbilden. Für C würde das beispielweise bedeuten: Iteriert wird z.B.
> über ein Array von Strukturen und diese Strukturen enthalten
> Funktionszeiger für all den Scheiß, der bei C++ als virtuelle Methoden
> auftaucht.

Ja, habe ich momentan. Das ist häßlich, und die Definition ist extrem 
fehleranfällig. (Entweder Identifikation des Datentyps über enum und 
Aufruf der ausführenden Funktion über switch/case oder eben über 
Funktionszeiger direkt im Array oder beides.) Da erhoffe ich mir über 
templates + virtual functions eine erhebliche Vereinfachung.

Vincent H. schrieb:
> Code landet "immer" in der .text Section eines Mikrocontrollers und
> damit im Flash. Egal von welcher Programmiersprache der kommt.
> [...]
> Ich glaub du würdest gerne andere Fragen stellen, bist aber noch nicht
> weit genug um diese formulieren zu können.

Ich meine nicht den Code, sondern das array. Wenn aus dem array ein 
Element im RAM landet, kann ich damit leben. Wenn das komplette array 
ins RAM geladen wird, bin ich am Ende.

c-hater schrieb:
> Das Problem ist eher, dass die Objekte selber Speicherplatz im RAM
> benötigen. Und virtuelle Methoden tragen hier sogar ziemlich dick auf...

Das heisst, konstante Objekte werden vor dem Lesen von Attributen 
grundsätzlich ins RAM kopiert? Oder vor dem Ausführen einer Methode? 
Oder wird nur der vtable ins RAM kopiert?

Nicht, dass das schlimm wäre... damit kann ich leben. Nur nicht mit 
allen gleichzeitig.

: Bearbeitet durch User
von Vincent H. (vinci)


Bewertung
0 lesenswert
nicht lesenswert
c-hater schrieb:
> Das Problem ist eher, dass die Objekte selber Speicherplatz im RAM
> benötigen. Und virtuelle Methoden tragen hier sogar ziemlich dick auf...

VTables brauchen keinen RAM. (außer wohl beim AVR?)

Walter T. schrieb:
> Das heisst, konstante Objekte werden vor dem Lesen von Attributen
> grundsätzlich ins RAM kopiert? Oder vor dem Ausführen einer virtuellen
> Funktion?

Es ist eigentlich ganz simpel. C++ tut was C tut.
Virtuelle Funktionen haben nichts mit dem RAM Verbrauch zu tun.

von Mark B. (markbrandis)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Vincent H. schrieb:
> Walter T. schrieb:
>> Gähn schrieb:
>>> Die kannst du dann in ein Array von Fahrzeugen
>>
>> Wenn ich die Objekte aus "Motorrad", "Auto" etc. in ein Array mit
>> "Fahrzeug" packe - gehen nicht die zusätzlichen Attribute von "Motorrad"
>> verloren? Wird dann nicht die Methode Auto::fahren() anstelle
>> Motorrad::fahren() aufgerufen (oder geht schief, wenn Auto::fahren() mir
>> eine virtuelle Funktion ist?
>
> Richtig, das ist mal wieder absoluter Blödsinn was hier geschrieben
> wird.

Wieso denn Blödsinn? Selbstverständlich ist es möglich, es so zu machen 
wie von Gähn beschrieben. Hier der Beweis: Code im Anhang und die 
Ausgabe siehe unten.
1
D:\Dateien\C++>g++ -Wall Fahrzeug.cpp -o Fahrzeug.exe
2
3
D:\Dateien\C++>Fahrzeug.exe
4
Ich bin der LKWuppdich
5
Mim Moped durch die Stadt
6
I want to ride my bicycle, I want to ride my bike
7
Fahrn Fahrn Fahrn auf der Autobahn

von C++ Kenner (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Wer statische Polymorphie möchte, nimmt std::variant und z.B. 
std::visit.

von C++ Kenner (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Vincent H. schrieb:
> c-hater schrieb:
>> Das Problem ist eher, dass die Objekte selber Speicherplatz im RAM
>> benötigen. Und virtuelle Methoden tragen hier sogar ziemlich dick auf...
>
> VTables brauchen keinen RAM. (außer wohl beim AVR?)
>
> Walter T. schrieb:
>> Das heisst, konstante Objekte werden vor dem Lesen von Attributen
>> grundsätzlich ins RAM kopiert? Oder vor dem Ausführen einer virtuellen
>> Funktion?
>
> Es ist eigentlich ganz simpel. C++ tut was C tut.
> Virtuelle Funktionen haben nichts mit dem RAM Verbrauch zu tun.

Natürlich brauchen vtables RAM. Selbst wenn du sie in C selber bastelst, 
brauchen sie RAM (function pointer). Die Information welcher Art ein 
Objekt ist, muss zur Laufzeit/dynamisch vorliegen. So funktioniert 
dynamic_cast und RTTI und deshalb hat es auch entsprechenden overhead. 
DU kannst deine Klasse auch gnu::packed machen und dann sizeof(...) 
vergleichen.

https://en.wikipedia.org/wiki/Virtual_method_table#cite_note-4
https://en.cppreference.com/w/cpp/language/virtual

von Rolf M. (rmagnus)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Das heisst, konstante Objekte werden vor dem Lesen von Attributen
> grundsätzlich ins RAM kopiert? Oder vor dem Ausführen einer Methode?
> Oder wird nur der vtable ins RAM kopiert?

Das kommt drauf an. Es gibt dafür keine Notwendigkeit, aber je nach 
Compiler und Architektur wird es ggf. trotzdem gemacht.

Vincent H. schrieb:
> c-hater schrieb:
>> Das Problem ist eher, dass die Objekte selber Speicherplatz im RAM
>> benötigen. Und virtuelle Methoden tragen hier sogar ziemlich dick auf...
>
> VTables brauchen keinen RAM. (außer wohl beim AVR?)

Sofern sich da nichts geändert hat, kopiert AVR-GCC beim Start alle 
vtables in den RAM. Das liegt aber im Wesentlichen daran, dass beim AVR 
RAM und Flash unterschiedlich angesprochen werden müssen und das im 
Compiler so nicht vorgesehen ist.

Mark B. schrieb:
> Wieso denn Blödsinn? Selbstverständlich ist es möglich, es so zu machen
> wie von Gähn beschrieben.

Nur arbeitet dein Code halt nicht so wie beschrieben. Er speichert keine 
Fahrzeuge im vector, sondern Zeiger.

C++ Kenner schrieb:
>> Es ist eigentlich ganz simpel. C++ tut was C tut.
>> Virtuelle Funktionen haben nichts mit dem RAM Verbrauch zu tun.
>
> Natürlich brauchen vtables RAM.

Sie brauchen Speicher. Ob das RAM sein muss, hängt nur von den 
Limitierungen des Compilers ab.

> Selbst wenn du sie in C selber bastelst, brauchen sie RAM (function
> pointer).

Nein. vtables ändern sich zur Laufzeit nicht. Und bei Objekten, die 
komplett konstant sind, spricht auch nichts dagegen, dass der 
vtable-Pointer das auch ist. In dem Fall wird exakt 0 RAM dafür 
benötigt.

> Die Information welcher Art ein Objekt ist, muss zur Laufzeit/dynamisch
> vorliegen.

Nur wenn das Objekt zur Laufzeit dynamisch erzeugt wurde. Und auch dann 
ist es nur der vtable-Pointer, nicht die ganze vtable, die im RAM stehen 
muss.

von Vincent H. (vinci)


Bewertung
0 lesenswert
nicht lesenswert
C++ Kenner schrieb:
> Wer statische Polymorphie möchte, nimmt std::variant und z.B.
> std::visit.

std::variant ist, auch wenn es beim dispatch via std::visit so aussieht, 
eigentlich keine statische Polymorphie.

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Danke für Deinen Beitrag! Es ist nicht ganz, was ich suche *), aber 
trotzdem interessant.

Mark B. schrieb:
> Hier der Beweis: Code im Anhang

Dieser Teil hier:
1
int main()
2
{
3
    LKW      *lkw1      = new LKW;
4
    Motorrad *motorrad1 = new Motorrad;
5
    Fahrrad  *fahrrad1  = new Fahrrad;
6
    PKW      *pkw1      = new PKW;
7
    
8
   
9
    Fahrzeug* fahrzeug_array[4] = { lkw1, motorrad1, fahrrad1, pkw1 };
10
    
11
    for (int i=0; i<4; i++)
12
    {
13
        fahrzeug_array[i]->fahren();
14
    }
15
    
16
    return 0;
17
}

wird in cppinsights zu:
1
int main()
2
{
3
  LKW * lkw1 = new LKW();
4
  Motorrad * motorrad1 = new Motorrad();
5
  Fahrrad * fahrrad1 = new Fahrrad();
6
  PKW * pkw1 = new PKW();
7
  Fahrzeug * fahrzeug_array[4] = {static_cast<Fahrzeug *>(lkw1), static_cast<Fahrzeug *>(motorrad1), static_cast<Fahrzeug *>(fahrrad1), static_cast<Fahrzeug *>(pkw1)};
8
  for(int i = 0; i < 4; i++) 
9
  {
10
    fahrzeug_array[i]->fahren();
11
  }
12
  
13
  return 0;
14
}
Es läuft also implizit ein static cast auf einen Zeiger auf einen Objekt 
der Klasse Fahrzeug, wenn das Zeiger-Array erzeugt wird. Wieso wird ein 
Zeiger, der auf ein Objekt der Klasse Fahrzeug zeigt, als Objekt der 
Klasse Motorrad dereferenziert?


*) letztendlich will ich am Ende eine Konstante mit tabellenartiger 
Initializer-Liste. Wenn ich tausend konstante Variablen mit Zeigern 
drauf erzeugen muss, wird es noch unübersichtlicher als sowieso schon. 
Aber ich gehe mal davon aus, dass das irgendwie --wie auch bei einem 
cstring-Array-- geht.

von Programmierer (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Wieso wird ein
> Zeiger, der auf ein Objekt der Klasse Fahrzeug zeigt, als Objekt der
> Klasse Motorrad dereferenziert?

Wird es nicht. Lediglich beim Aufruf von virtuellen Funktionen wird beim 
Aufruf eben die Funktion der abgeleiteten Klasse aufgerufen. Sie muss 
dazu aber auch in der Basisklasse deklariert sein.

Walter T. schrieb:
> *) letztendlich will ich am Ende eine Konstante mit tabellenartiger
> Initializer-Liste.

Führ das doch mal genauer aus. Vielleicht kann man eine bessere 
Möglichkeit finden.

von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> *) letztendlich will ich am Ende eine Konstante mit tabellenartiger
> Initializer-Liste.

https://youtu.be/zBkNBP00wJE

Oliver

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Programmierer schrieb:
> Führ das doch mal genauer aus. Vielleicht kann man eine bessere
> Möglichkeit finden.

In Pseudocode will ich irgendwann einmal etwas wie das hier 
implementiert haben:
1
// Konstantes Array im Flash
2
const Gfxobj MainScreen[] =
3
{
4
    Circle({.x=0, .y=10, .r=20, .color=color::red, .bgcolor::red }),
5
    Rectangle({.x=100, .y=10, .w=20, .h=10, .color=color::blue }),
6
    Textfield({.x=100, .y=10, .w=20, .h=10, .color=color::black, .s = "Hallo" }),
7
    ...
8
}
9
10
int main()
11
{
12
    ...
13
14
    for( auto Obj : MainScreen )
15
    {
16
        Obj->draw();
17
    }
18
19
    ...
20
}

Sprich: große, konstate Arrays mit gemischten Typen und darüber 
iterieren können.

Aber ich suche die Lösung noch gar nicht jetzt. Ich bin bei meinem 
C++-Buch erst bei Seite 135. Der Zweck des Threads war nur, abschätzen 
zu können, ob ich hier überhaupt in die richtige Richtungs ziele (und 
nach Seite 1007 die Lösung finden können werde).

Sprich: Ob es für mein aktuelles Problem sinnvoller ist, C++ zu lernen, 
oder meine Zeit lieber darin zu investieren, einen Codegenerator für C 
zu schreiben.

von cppbert3 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Könnt ihr mir noch verraten, ob es prinzipiell möglich ist, diese Liste
> komplett im Flash eines Mikrocontrollers zu halten? (Auf Anhieb sehe ich
> da kein ernsthaftes Hindernis, aber ich kenne die Möglichkeiten der
> Sprache noch nicht.)

falls es das deine Frage ist - man kann die Objekte auch nur auf dem 
Stack, oder global anlegen - falls deinen Objekte nicht auf dem Heap 
liegen müssen
1
#include <array>
2
#include <cstdio>
3
#include <memory>
4
5
struct Fahrzeug {
6
  virtual void foo() const { puts(__PRETTY_FUNCTION__); }
7
};
8
9
struct Auto : Fahrzeug {
10
  void foo() const final { puts(__PRETTY_FUNCTION__); }
11
};
12
13
struct LKW : Fahrzeug {
14
  void foo() const final { puts(__PRETTY_FUNCTION__); }
15
};
16
17
Auto f1;
18
19
int main() {
20
  std::array<Fahrzeug*, 3u> fahrzeuge{};
21
22
  LKW f2;
23
  Auto f3;
24
25
  fahrzeuge[0u] = &f1;
26
  fahrzeuge[1u] = &f2;
27
  fahrzeuge[2u] = &f3;
28
29
  fahrzeuge[0u]->foo(); // Auto::foo
30
  fahrzeuge[2u]->foo(); // LKW::foo
31
  fahrzeuge[1u]->foo(); // Auto::foo
32
}

von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Wenn alles konstant ist, kann man das auch statisch machen:
1
#include <iostream>
2
#include <tuple>
3
4
class Auto
5
{
6
public:
7
  constexpr void foo() const { puts(__PRETTY_FUNCTION__); }
8
};
9
10
class Lkw
11
{
12
public:
13
  constexpr void foo() const { puts(__PRETTY_FUNCTION__); }
14
};
15
16
int main()
17
{
18
  Auto car;
19
  Lkw lkw;
20
  const std::tuple<Auto&, Lkw&> myTuple{car, lkw};
21
22
  std::get<0>(myTuple).foo();
23
  std::get<1>(myTuple).foo();
24
25
}

Oliver

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Die Frage ist: Kann ich ein globales Array mit anonymen Variablen 
anlegen, wenn diese einen unterschiedlichen Typ haben?

von Programmierer (Gast)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Sprich: große, konstate Arrays mit gemischten Typen und darüber
> iterieren können.

Das Problem ist, dass die unterschiedlichen Typen unterschiedlich groß 
sind, und daher nicht in ein Array gepackt werden können, denn wie soll 
die Adresse von Element X berechnet werden, wenn nicht mit X*sizeof(T) ? 
Daher der Trick, dass im Array nur der Zeiger steht, denn alle Zeiger 
sind gleich groß. Das impliziert aber, dass die eigentlichen Objekte 
noch irgendwo anders abgelegt sind, wo der Zeiger dann hin zeigt. Auf 
dem Heap anlegen via "new" geht zwar, ist aber für Mikrocontroller 
ineffizient, und wenn die ganze Struktur sowieso fix ist, auch unnötig. 
Am Logischsten wäre es, vor "MainScreen" alle Objekte einmal manuell zu 
definieren, und im Array dann Pointer darauf anzulegen. Das wäre 
allerdings eine Menge Boilerplate-Code.

Daher habe ich mal aus Spaß im Anhang eine Klasse PolymorphicContainer 
implementiert, welche die Objekt-Instanzen in einem Tupel ablegt, und in 
einem Array die Pointer darauf. Damit kann man den Code fast genau so 
hinbekommen wie du ihn haben wolltest:
1
constexpr PolymorphicContainer MainScreen {
2
  Circle({.x=0, .y=10, .r=20, .fgcolor=color::red, .bgcolor=color::red }),
3
  Rectangle({.x=100, .y=10, .w=20, .h=10, .fgcolor=color::blue }),
4
  Textfield({.x=100, .y=10, .w=20, .h=10, .fgcolor=color::black, .s="Hallo" }),
5
};
6
7
int main () {
8
  for (const Geo* obj : MainScreen.array) {
9
    obj->draw ();
10
  }
11
}

Die designated Initializer erfordern C++20. So kannst du das Array 
iterieren und virtuelle Funktionen wie gehabt nutzen. Falls gewünscht 
kannst du per "std::get<0> (MainScreen.tuple)" o.ä. direkt auf bestimmte 
Elemente zugreifen und erhältst dann auch den konkreten Typ, allerdings 
muss der Index fix und dem Compiler bekannt sein.

Zum "Vergleich" habe ich noch die Funktion "staticIteration" 
mitgeliefert, mit der man durch das Tupel iterieren kann, ohne den Umweg 
über das Array und Zeiger:
1
staticIteration (MainScreen.tuple, [&] (const auto& elem) {
2
  elem.draw ();
3
});

Die Variable "elem" hat dabei immer den konkreten Typ, somit kann man 
auf die spezifischen Elemente der abgeleiteten Klasse zugreifen. Somit 
werden hier keine virtuellen Funktionen gebraucht. Allerdings wird hier 
keine Schleife erzeugt, sondern es wird für jedes Element im Tupel 
Code für den Aufruf generiert. Dadurch kann der Code sehr schnell sehr 
groß werden, was dann nicht unbedingt geeignet ist.

von cppbert3 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Die Frage ist: Kann ich ein globales Array mit anonymen Variablen
> anlegen, wenn diese einen unterschiedlichen Typ haben?

du hast mindestens 4 Antworten dazu

von cppbert3 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Programmierer schrieb:
> Daher habe ich mal aus Spaß im Anhang eine Klasse PolymorphicContainer
> implementiert,

Die 100 Punkte für maximale Anfängerverwirrung gehen definitiv an dich 
:)

@Walter T.

einfach ignorieren :)

von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Die Frage ist ungenau gestellt. Was sind "anonyme Variablen"?

Ein Array kann nur Elemente von selben Typ speichern, das ist in C++ 
nicht anders als in C. Der Typ kann aber, wie oben in den Beispielen, 
ein Pointer oder eine Referenz auf eine Basisklasse sein, während die 
dazugehörigen Instanzen abgeleitete Klassen der Basisklasse sind.

Oliver

von cppbert3 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
std::array<Fahrzeug*, 3u> fahrzeuge{};

entspricht

Fahrzeug* fahrzeug[3];

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Programmierer schrieb:
> [...]
Danke, dafür werde ich mir wohl noch etwas Zeit nehmen müssen, bis ich 
das verstehe.

Programmierer schrieb:
> Das Problem ist, dass die unterschiedlichen Typen unterschiedlich groß
> sind, und daher nicht in ein Array gepackt werden können, denn wie soll
> die Adresse von Element X berechnet werden, wenn nicht mit X*sizeof(T) ?

Oliver S. schrieb:
> Die Frage ist ungenau gestellt. Was sind "anonyme Variablen"?

Meinem Verständnis nach analoges Beispiel in C:
1
const char *const MyList[3] = {"Text1", "Text2", "Text3"};
List ist ein Pointer Array auf anonyme Variablen gleichen Typs 
unterschiedlicher Größe, lässt sich aber in einem Rutsch hinschreiben.

In C ein Beispiel mit einem Pointer Array auf anonyme Variablen 
unterschiedlichen Typs, das sich in einem Rutsch hinschreiben lässt, und 
mit dem man auch noch etwas anfangen kann, bekomme ich gerade nicht hin.

: Bearbeitet durch User
von Programmierer (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Meinem Verständnis nach analoges Beispiel in C:

String-Literale sind ein Sonderfall, weil deren Lebenszeit von C/C++ 
automatisch global gemanagt wird.

Walter T. schrieb:
> In C ein Beispiel mit einem Pointer Array auf anonyme Variablen
> unterschiedlichen Typs, dass sich in einem Rutsch hinschreiben lässt,
> bekomme ich gerade nicht hin.

Auch mit gleichem Typ geht es nicht, außer bei String-Literalen... Du 
musst die Variablen irgendwo hin ablegen. Meine 
PolymorphicContainer-Klasse vereinfacht das. Mehrfach malloc/new nutzen 
geht natürlich auch, aber das ist für solche Konstrukte eben Overhead.

Walter T. schrieb:
> Danke, dafür werde ich mir wohl noch etwas Zeit nehmen müssen, bis ich
> das verstehe.

Du kannst die Klasse auch so verwenden wie sie ist ohne die Details zu 
verstehen, ist ja bei Nutzung von std::vector etc auch nicht anders, 
dessen Innereien sind auch schwer verdaulich. Die Verwendung ist ja am 
Beispiel ersichtlich.

von Vincent H. (vinci)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Die Frage ist: Kann ich ein globales Array mit anonymen Variablen
> anlegen, wenn diese einen unterschiedlichen Typ haben?

Nein, siehe oben.

Du kannst aber einen heterogenen Container anlegen.
1
#include <cstdio>
2
#include <tuple>
3
4
struct A {
5
  void foo() const { puts(__PRETTY_FUNCTION__); }
6
};
7
8
struct B {
9
  void foo() const { puts(__PRETTY_FUNCTION__); }
10
};
11
12
struct C {
13
  void foo() const { puts(__PRETTY_FUNCTION__); }
14
};
15
16
constexpr std::tuple abc{A{}, B{}, C{}};
17
18
int main() {
19
  std::get<A>(abc).foo();
20
  std::get<B>(abc).foo();
21
  std::get<C>(abc).foo();
22
}

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Sehr ich das richtig, dass ich über ein tuple nicht in einer 
for-Schleife mit einer Auto-Variable drüberiterieren kann, und bei der 
Initialisierung an zwei Stellen (in der Deklaration und in der 
Initializer List) Buch führen muss, um welchen Datentyp es sich handelt?

Das würde es für eine Liste mit > 100 Einträgen eigentlich schon direkt 
disqualifizieren.

Irgendwie fürchte ich gerade, dass ich so oder so um einen Codegenerator 
nicht herumkomme.

: Bearbeitet durch User
Beitrag #6486949 wurde von einem Moderator gelöscht.
von Programmierer (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Sehr ich das richtig, dass ich über ein tuple nicht in einer
> for-Schleife mit einer Auto-Variable drüberiterieren kann

Richtig.

Walter T. schrieb:
> und bei der
> Initialisierung an zwei Stellen (in der Deklaration und in der
> Initializer List) Buch führen muss, um welchen Datentyp es sich handelt?

Auch richtig. Daher macht man schlauerweise beides in einem.

Walter T. schrieb:
> Das würde es für eine Liste mit > 100 Einträgen eigentlich schon direkt
> disqualifizieren.

Daher kann man es sich mit Wrappern einfacher machen, wie das gezeigte 
PolymorphicContainer.

Die gezeigte staticIteration Funktion ermöglicht ein Iterieren über die 
Elemente eines tuple, aber das ist keine echte Schleife, im erzeugten 
Code wird das komplett ausgerollt.

Walter T. schrieb:
> Irgendwie fürchte ich gerade, dass ich so oder so um einen Codegenerator
> nicht herumkomme.

Doch. Wie gezeigt ist das Problem lösbar. Man darf aber nicht erwarten 
dass das mit einfachsten Mitteln direkt geht. Code-Generatoren sind im 
Endeffekt meistens mehr Arbeit als sie helfen.

Beitrag #6486952 wurde von einem Moderator gelöscht.
Beitrag #6486955 wurde von einem Moderator gelöscht.
von Vincent H. (vinci)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Sehr ich das richtig, dass ich über ein tuple nicht in einer
> for-Schleife mit einer Auto-Variable drüberiterieren kann, und bei der
> Initialisierung an zwei Stellen (in der Deklaration und in der
> Initializer List) Buch führen muss, um welchen Datentyp es sich handelt?

In einem range-for geht das nicht nein, da die Typen unterschiedlich 
sind und sich keinen Iterator teilen. In einem generischen Algorithmus 
geht das schon.

Walter T. schrieb:
> Das würde es für eine Liste mit > 100 Einträgen eigentlich schon direkt
> disqualifizieren.
>
> Irgendwie fürchte ich gerade, dass ich so oder so um einen Codegenerator
> nicht herumkomme.

Nachdem du nachwievor kein Wort darüber verlierst was du vor hast wird 
dir niemand weiterhelfen können. Fakt ist, dass C++ aktuell über keine 
Reflection verfügt. Die Möglichkeiten der Metaprogrammierung werden zwar 
ständig ausgebaut, sind aber dadurch trotzdem noch begrenzt.

Beitrag #6486957 wurde von einem Moderator gelöscht.
Beitrag #6486965 wurde von einem Moderator gelöscht.
von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Vincent H. schrieb:
> Nachdem du nachwievor kein Wort darüber verlierst was du vor hast

Ich habe es oben angedeutet:
Beitrag "Re: C++ : Mit for-Schleife über Objekte verschiedener Klassen iterieren"

Man stelle sich die Tabelle/Array/Sonstwas mit > 200 Einträgen vor. 
Viele Variablen unterschiedlichens Typs, aber alle jeweils mit einer 
Funktion gleichens Namens.

Vielleicht sind die "polymorphic container" genau die Lösung. Ich muss 
sie mir mal in Ruhe angucken.

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Geben wir Moby für die nächsten drei Stunden seine Spielwiese, um sich 
auszutoben. Ich muss eh noch mein Buch weiterlesen.

von Vincent H. (vinci)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Man stelle sich die Tabelle/Array/Sonstwas mit > 200 Einträgen vor.
> Viele Variablen unterschiedlichens Typs, aber alle jeweils mit einer
> Funktion gleichens Namens.

Der Übersichtlichkeit halber auf godbolt ->
https://godbolt.org/z/Yxn97e

: Bearbeitet durch User
Beitrag #6486988 wurde von einem Moderator gelöscht.
von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Vincent H. schrieb:
> Der Übersichtlichkeit halber auf godbolt ->
> https://godbolt.org/z/Yxn97e


Ah, ich sehe meinen Fehler:
1
constexpr std::tuple abc{A{}, B{}, C{}};
Ich habe händisch ergänzt:
1
constexpr std::tuple<A, B, C> abc{A{}, B{}, C{}};
und mit C++17 kompiliert. C++20 erfordert offensichtlich nicht mehr die 
Vorab-Auflistung des Datentyps im Tupel.

Das ist ja regelrecht brauchbar. Ich bin entzückt.

Jetzt gönne ich mir erst einmal im Laufe des Tages ein 
Toolketten-Update.

: Bearbeitet durch User
von Vincent H. (vinci)


Bewertung
0 lesenswert
nicht lesenswert
Genannt CTAD:
https://en.cppreference.com/w/cpp/language/class_template_argument_deduction

/edit
Sollt mit C++17 eigentlich auch gehn.

: Bearbeitet durch User
von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Ist hier gerade ein Beitrag zuviel gelöscht worden? Ich erinnere mich an 
o

Vincent H. schrieb:
> Sollt mit C++17 eigentlich auch gehn.

Tut es auch. Ich habe es angepasst, bevor ich das Kompilieren probiert 
habe, weil mein Buch "ab C++20" dazu schreibt.

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Danke für alle hilfreichen Tipps!

Diese Diskussion hat dafür gesorgt, dass ich Licht am Ende des Tunnels 
sehe!

von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Diene  Fragen lassen ja eher vermuten, daß du noch auf dem Weg zum 
Tunneleingang bist. Das Licht am Ende des C++ - Tunnels ist noch weit 
weg.

Oliver

von Mark B. (markbrandis)


Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Mark B. schrieb:
>> Wieso denn Blödsinn? Selbstverständlich ist es möglich, es so zu machen
>> wie von Gähn beschrieben.
>
> Nur arbeitet dein Code halt nicht so wie beschrieben. Er speichert keine
> Fahrzeuge im vector, sondern Zeiger.

Es war doch zu Beginn von einem Array die Rede:

Walter T. schrieb:
> diese in eine Art array zu packen und mit einer for-Schleife
> darüberzuiterieren?

Wenn es ein Array sein soll, geht es nur so. Wenn es auch eine andere 
Datenstruktur sein kann, geht es auch anders. Ich denke der 
Themenersteller ist noch nicht so weit um genau entscheiden zu können, 
was denn für seinen Anwendungsfall besser geeignet wäre. ;-)

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Ich habe "eine Art Array" für unspezifisch genug gehalten. In Matlab 
heißen die "Cell Array", in Python "list". Was in C++ der passende 
Datentyp ist, war ja gerade die Frage. Eine geordnete Menge 
unterschiedlicher Datentypen.

In Matlab sind die "Cell Arrays" von der Nutzung wie structs mit Index 
anstelle Schlüsselwort. Also das absolute Gegenteil von exotisch. (Aber 
Matlab ist ohnehin etwas außergewöhnlich. Dort kann man auch sehr bequem 
über die Felder von structs iterieren.) In Python haben "lists" auch 
keinen Seltenheitswert. In C++ scheint das Anliegen so exotisch zu sein, 
dass es schwierig ist, die Frage danach zu formulieren.

: Bearbeitet durch User
von Mark B. (markbrandis)


Bewertung
0 lesenswert
nicht lesenswert
C++ erlaubt grundsätzlich recht viele verschiedene Ansätze, ein und 
dasselbe Problem zu lösen. Die Komplexität dieser Sprache ist schon sehr 
hoch.

von Oliver S. (oliverso)


Bewertung
-1 lesenswert
nicht lesenswert
Walter T. schrieb:
> In C++ scheint das Anliegen so exotisch zu sein,
> dass es schwierig ist, die Frage danach zu formulieren.

Die Frage ist nur deshalb schwierig zu formulieren, weil du anscheinend 
nicht weißt, was du eigentlich fragen willst.

Eine Antwort auf deine nicht gestellte Frage ist: C++ ist wie C eine 
streng typisierte Sprache, Matlab und Python sind das nicht.

Oliver

von Walter T. (nicolas)


Bewertung
2 lesenswert
nicht lesenswert
Oliver S. schrieb:
> Die Frage ist nur deshalb schwierig zu formulieren, weil du anscheinend
> nicht weißt, was du eigentlich fragen willst.

Wenn man einen Ausdruck so präzise formulieren kann, dass ihn ein 
Compiler versteht, benötigt man kein Forum.

von Oliver S. (oliverso)


Bewertung
1 lesenswert
nicht lesenswert
Walter T. schrieb:
> Irgendwie fürchte ich gerade, dass ich so oder so um einen Codegenerator
> nicht herumkomme.

WEnn es um hunderte von konstanten Objekten geht, ist das sowieso das 
Mittel der Wahl.

Nur ein Hinweis am Rande:

Die statischen, zur Compilezeit ausgewerteten Datentypen wie tuple 
brauchen genau das, nämlich Compiletime. Die aktuellen Compiler sind 
inzwischen zwar dafür gerüstet, aber ein tuple mit ein paar hundert 
Elementen könnte da doch zu unliebsamen Überraschungen führen.

Ein gcc 4.1 (oder so) crasht schon bei mehr als einer handvoll 
Elementen...

Oliver

von Dirk K. (merciless)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Ich habe "eine Art Array" für unspezifisch genug gehalten. In Matlab
> heißen die "Cell Array", in Python "list". Was in C++ der passende
> Datentyp ist, war ja gerade die Frage. Eine geordnete Menge
> unterschiedlicher Datentypen.
>
> In Matlab sind die "Cell Arrays" von der Nutzung wie structs mit Index
> anstelle Schlüsselwort. Also das absolute Gegenteil von exotisch. (Aber
> Matlab ist ohnehin etwas außergewöhnlich. Dort kann man auch sehr bequem
> über die Felder von structs iterieren.) In Python haben "lists" auch
> keinen Seltenheitswert. In C++ scheint das Anliegen so exotisch zu sein,
> dass es schwierig ist, die Frage danach zu formulieren.

Normalerweise würde ich dafür eine
std::list verwenden und Zeiger auf
die Basisklasse darin speichern. Je
nachdem, welche Anforderungen du noch
an den Container hast (Zugriff per Key?,
Sortierung?, etc.), kämen noch andere
in Betrachtung: std::map, std::set,
std::vector.

merciless

von Sven B. (scummos)


Bewertung
1 lesenswert
nicht lesenswert
Die Frage ist halt, was willst du denn im List Body dann mit den 
Objekten machen? Das Äquivalent zur Python list wäre vmtl sowas wie 
std::vector<std::any>. Da passen die Objekte leicht rein, und man kann 
auch leicht darüber iterieren, nur danach irgendwas damit zu tun wird 
schwer.

Etwas sinnvoller wäre ein std::vector<std::variant<...>>. Mit std::visit 
kann man dann relativ lesbaren Code schreiben, der die einzelnen Typen 
z.B. per function template specialization behandelt.

Oder eben die bereits vorgeschlagene Vererbungshierarchie. Für dein 
Beispiel mit den Geometrieelementen scheint mir das die geeignetste 
Lösung. Sind dir die vtables zu groß, kannst du sie dir selber 
schreiben, z.B. per templates.

von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Dirk K. schrieb:
> std::list

Wie selbst der C++-Standard selber schreibt, ist std::vector die beste 
Wahl für die allermeisten Anwendungen. So wohl auch hier.

Alles in allem klingt die Anwendung von Walter aber sehr danach, daß er 
ein „Array“ von zur Compilezeit bekannten konstanten Elemente sucht.

Da ist std::tuple schon nicht verkehrt, dann gibt es gar keinen run time 
overhead.

Oliver

von Sven B. (scummos)


Bewertung
0 lesenswert
nicht lesenswert
Ich finde, ziemlich viele Anwendungen von std::array lassen sich durch 
eine initializer list ersetzen, z.B. der oben genannte.

auto const items = {1, 2, 3, 4};
for (auto item: items) { ... }

von Wilhelm M. (wimalopaan)


Bewertung
-1 lesenswert
nicht lesenswert
Wenn die Menge der Typen zur Compilezeit bekannt ist, kommt ggf. 
std::variant noch ins Spiel ;-)

von Dirk K. (merciless)


Bewertung
0 lesenswert
nicht lesenswert
Oliver S. schrieb:
> Dirk K. schrieb:
>> std::list
>
> Wie selbst der C++-Standard selber schreibt, ist std::vector die beste
> Wahl für die allermeisten Anwendungen. So wohl auch hier.
Jein, wenn z.B. viele Inserts() gemacht werden,
ist std::vector ungeeignet, weil sonst zu oft
umkopiert werden muss (std::vector garantiert,
dass die Einträge im Speicher hintereinander
liegen. Ist der anfänglich reservierte Speicher
voll, wird neu allokiert und der ganze Inhalt
umkopiert.). Noch schlimmer wird es beim
Löschen von Einträgen. std::list wäre in dem
Fall performanter.

merciless

von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Dirk K. schrieb:
> Jein, wenn z.B. viele Inserts() gemacht werden,
> ist std::vector ungeeignet,

Deshalb gibt es std::list, und für andere Anwendungsfälle auch noch die 
andere Container, was ja völlig ok ist.

In einer älteren Version des Standards oder in seinem Umfeld stand m.E 
sinngemäß mal drin:

Nimm immer std::vector. Wenn nicht, musst du es ausführlich begründen.

Die Anwendung des TO sieht jetzt nicht danach aus, daß er was anderes 
braucht.

Oliver

von Mark B. (markbrandis)


Bewertung
0 lesenswert
nicht lesenswert
Oliver S. schrieb:
> Nimm immer std::vector. Wenn nicht, musst du es ausführlich begründen.

Wenn die Menge an Objekten sowieso fest ist und sich zur Laufzeit nicht 
ändert, wozu dann ein Vector? Dann tut es doch auch ein schnödes Array?

von Walter T. (nicolas)


Bewertung
0 lesenswert
nicht lesenswert
Oliver S. schrieb:
> https://youtu.be/zBkNBP00wJE

Das ist ein sehr schöner Vortrag. Danke für den Tipp!

Dieser Beitrag kann nur von angemeldeten Benutzern beantwortet werden. 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, Yahoo oder Facebook? Keine Anmeldung erforderlich!
Mit Google-Account einloggen | Mit Facebook-Account einloggen
Noch kein Account? Hier anmelden.