std::vector mit unterschiedliche grossen Datentypen
-
In der Referenz https://en.cppreference.com/w/cpp/container/vector steht:
The elements are stored contiguously, which means that elements can be accessed not only through iterators, but also using offsets to regular pointers to elements. This means that a pointer to an element of a vector may be passed to any function that expects a pointer to an element of an array.
Also angenommen es gibt eine Basisklasse Foo und eine davon abgeleitete Klasse Bar:
class Foo { public: int a, b; }; class Bar : public Foo { public: int c, d, e, f; };
Diese habe eine unterschiedliche Grösse, einmal 8 und einmal 24.
std::vector<Foo> store; store.push_back(Bar());
Das geht, weil ein Objekt von der Klasse Bar implizit in Objekt der Klasse Foo upgecastet wird. Was ich dabei aber nicht verstehe, wie wird sichergestellt, dass der Speicher contiguously bleibt, wenn die Grösse unterschiedlich ist.
-
@titan99_ sagte in std::vector mit unterschiedlichen Grössen:
wie wird sichergestellt, dass der Speicher contiguously bleibt, wenn die Grösse unterschiedlich ist.
Gar nicht. Im vector sind nur Foo. Stichwort Object slicing.
-
@manni66 Danke für die rasche Antwort. Die Daten gehen also verloren. Das war mir neu.
#include <iostream> #include <vector> class Foo { public: Foo(int a, int b) : a(a), b(b) {} int a, b; }; class Bar : public Foo { public: Bar(int a, int b, int c, int d, int e, int f) : Foo(a, b), c(c), d(d), e(e), f(f) {} void print() { std::cout << a << " " << b << " " << c << " " << d << " " << e << " " << f << '\n'; } int c, d, e, f; }; int main(int argc, char** argv) { Bar bar0(0, 1, 2, 3, 4, 5); Bar bar1(6, 7, 8, 9, 10, 11); std::cout << "bar0 "; bar0.print(); std::cout << "bar1 "; bar1.print(); std::vector<Foo> store; store.push_back(bar0); store.push_back(bar1); auto downcast0 = static_cast<Bar*>(&store[0]); auto downcast1 = static_cast<Bar*>(&store[1]); std::cout << "downcast0 "; downcast0->print(); std::cout << "downcast1 "; downcast1->print(); system("pause"); }
-
Mit C++17 kann man std::variant verwenden:
#include <variant> #include <vector> #include <iostream> class Foo { public: int a, b; }; class Bar : public Foo { public: int c, d, e, f; }; using val = std::variant<Foo,Bar>; int main() { std::vector<val> v; v.push_back(Foo{1,2}); v.push_back(Bar{1,2,3,4,5,6}); Foo f = std::get<Foo>(v[0]); Bar b = std::get<Bar>(v[1]); std::cout << b.c << "\n"; }
-
@manni66 Ok. Also im Moment frage ich mich, für was es dann upcasting und downcasting gibt. Also ich bin am überlegen, einen std::vector mit smart_pointern zu verwenden, oder können dann auch Daten verloren gehen?
Oder gibt es geeignetere Container als vector für Klassen mit gemeinsamer Basisklasse?
Edit: Also in meinem konkreten Fall ist es glaube ich gar nicht so schlimm, da alle Daten in der Basisklasse gespeichert sind oder sogar mit OpenGL. Also ich habe shapes, z.B. QuadShape, Circle usw. Aber dass einfach Daten verloren gehen können, wenn man nicht aufpasst...
-
@titan99_ sagte in std::vector mit unterschiedliche grossen Datentypen:
@manni66 Danke für die rasche Antwort. Die Daten gehen also verloren. Das war mir neu.
Was heisst "geht verloren"? Wird ja gar nicht erst gespeichert, ist ja gar kein Code dazu da. std::vector<Foo> wird auch komplett ohne die Kenntnisvon Bar kompiliert. Der Fehler ist das dann so komisch rumzucasten, weswegen man das ja auch nicht machen sollte.
-
@titan99_ sagte in std::vector mit unterschiedliche grossen Datentypen:
Oder gibt es geeignetere Container als vector für Klassen mit gemeinsamer Basisklasse?
Das hat nichts mit dem Container zu tun.
Foo f = Bar{1,2,3,4,5,6};
macht das gleiche. f ist kein Bar!
-
Habe es noch mit
std::shared_ptr
versucht:int main(int argc, char** argv) { std::shared_ptr<Bar> bar0ptr = std::make_shared<Bar>(Bar(0, 1, 2, 3, 4, 5)); std::shared_ptr<Bar> bar1ptr = std::make_shared<Bar>(Bar(6, 7, 8, 9, 10, 11)); std::cout << "bar0 "; bar0ptr->print(); std::cout << "bar1 "; bar1ptr->print(); std::vector<std::shared_ptr<Foo>> store; store.push_back(bar0ptr); store.push_back(bar1ptr); auto downcast0 = std::static_pointer_cast<Bar>(store[0]); auto downcast1 = std::static_pointer_cast<Bar>(store[1]); std::cout << "downcast0 "; downcast0->print(); std::cout << "downcast1 "; downcast1->print(); }
Also so blieben alle Daten erhalten. Frage mich aber ob die Daten nur zufällig nicht verloren gehen. Richtig erklären kann ich es mir nicht ganz.
-
@titan99_ Verstehst du den Unterschied zwischen einem Objekt und einem Zeiger auf das Objekt?
-
@hustbaer Ja einigermassen. Also Objekte/Variablen haben eine Adresse und die Daten. Bei zusammenhängendem Speicher sind die Adressen zusammenhängend. Es wird glaube ich jeweils ein Byte adressiert, aber genau weiss ich es nicht.
Edit: Also wenn ich die Zeiger oder die Adressen im vector speichere, sind nur die Variablen wo die Adressen gespeichert sind im Speicher zusammenhängend.
-
@titan99_ sagte in std::vector mit unterschiedliche grossen Datentypen:
Edit: Also wenn ich die Zeiger oder die Adressen im vector speichere, sind nur die Variablen wo die Adressen gespeichert sind im Speicher zusammenhängend.
Bingo.
-
Beachte auch:
struct Foo { virtual void p() { cout << "Foo.\n"; } }; struct Bar : public Foo { void p() override { cout << "Bar.\n"; } }; Bar b; Foo f = b; f.p(); // Foo. //Aber: Bar *pb = new Bar; Foo *pf = pb; pf->p(); //Bar.
(Natürlich sonst auch an virtual ~Foo denken und Smartptr verwenden)
-
weiss nicht ob das sinnvoll ist, aber wenn man will dass alle objekte gleich groß sind, dann könnte man sie in eine union stecken. dabei gibt das größte objekt die größe vor.
-
@Bushmaster sagte in std::vector mit unterschiedliche grossen Datentypen:
weiss nicht ob das sinnvoll ist,
Nein. std::variant.
-
@titan99_ sagte in std::vector mit unterschiedliche grossen Datentypen:
Also im Moment frage ich mich, für was es dann upcasting und downcasting gibt. Also ich bin am überlegen, einen std::vector mit smart_pointern zu verwenden, oder können dann auch Daten verloren gehen?
Mit Smart Pointern kannst du das Problem umgehen.
-
@manni66 sagte in std::vector mit unterschiedliche grossen Datentypen:
@Bushmaster sagte in std::vector mit unterschiedliche grossen Datentypen:
weiss nicht ob das sinnvoll ist,
Nein. std::variant.
die idee hatte schon jemand; https://en.cppreference.com/w/cpp/utility/variant
The class template std::variant represents a type-safe union.
wurde aber erst 2017 umgesetzt.
-
@eigenartig sagte in std::vector mit unterschiedliche grossen Datentypen:
Mit Smart Pointern kannst du das Problem umgehen.
Quatsch. Das hat nix mit smarten Pointern zu tun sondern mit Laufzeitpolymorphie. Und das geht eben definitionsgemäß nur wenn man Referenzen (Pointer oder Referenzen) auf die verschiedenen Objekte hat. Man darf diese Pointer dann unter gewissen umstänten casten.
-
@manni66 sagte in std::vector mit unterschiedliche grossen Datentypen:
@Bushmaster sagte in std::vector mit unterschiedliche grossen Datentypen:
weiss nicht ob das sinnvoll ist,
Nein. std::variant.
variant ist cool. visual studio kennt es, nachdem c++17 in den compiler settings aktiviert wurde.
int main() { variant<int, double, char, long, string, char*> v; cout << sizeof(v) << endl; // 40 bytes wtf? v = 1234; auto val = get_if<int>(&v); cout << typeid(*val).name() << ": " << *val << endl;; v = 1234.5678; auto val2 = get_if<double>(&v); cout << typeid(*val2).name() << ": " << *val2 << endl;; }
bisschen viel overhead, aber das liegt wohl daran weil ich für debug compiliert habe.
-
Eher wegen dem string und SSO.
-
@Mechanics sagte in std::vector mit unterschiedliche grossen Datentypen:
Eher wegen dem string und SSO.
aber ich tue keinen string rein. die typenliste gibt doch nur an, was hinein darf.
-
@Bushmaster Und string mit SSO ist das größte was hinein kann und damit ist ein variant auch mindesten so groß. (und die Größe steht natürlich schon zur compiletime fest.)