Hallo zusammen Ich habe vor, einen vector Container für kleine Controller zu schreiben, die sich ähnlich wie std::vector verhält, nur ohne Exceptions und dynamischen Speicher. Ich möchte, dass Objekte von Klassen gespeichert werden können, die keinen default-Konstruktor besitzen. Auch, dass die Objekte zerstört werden, wenn sie aus dem Container entfernt werden. Ich habe einen kleinen Entwurf der Grundfunktionen erstellt, nur um zu prüfen, ob mein Vorhaben überhaupt realisierbar ist (Code im Anhang). vector_base: Dies ist die Basisklasse, die sämtliche Funktionen implementiert. Sie dient als Basisklasse für vector, damit Vektoren verschiedener Grösse per Referenz an Funktionen übergeben werden können. vector: Stellt den Speicher als unsigned char array für vector_base zur Verfügung. Nun meine Fragen: Ist das alles in C++ so zulässig oder führt das zu undefiniertem Verhalten? Schon alleine beim reinterpret_cast im Konstruktor von vector_base (Zeile 62) bin ich mir nicht sicher. Dann auch die Zuweisungen in der Funktion push_back (Zeile 23 und 35). Ich weise einem nichtexistenten Objekt einen Wert zu. Normalerweise führt ein Zugriff auf zerstörte Objekte zu undefiniertem Verhalten. Ist das alles nur Murks was ich da mache? Wenn ja, welche Alternativen existieren? EDIT: Ausgaben auf std::cout sind erwartungsgemäss: constructor move assignment destructor destructor
:
Bearbeitet durch User
Be S. schrieb: > welche Alternativen > existieren? Um Exceptions loszuwerden kannst du auch mit
1 | -fno-exceptions |
kompilieren - mit allem was daraus folgt: https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_exceptions.html
Be S. schrieb: > Ich habe vor, einen vector Container für kleine Controller zu schreiben, > die sich ähnlich wie std::vector verhält, nur ohne Exceptions und > dynamischen Speicher. Den dynamischen Speicher kann man auch loswerden indem man einen Allokator an std::vector übergibt. Eine "richtige" vector Implementierung ist sehr kompliziert, aber eine gute Übung. Wird z.B. im Buch "The C++ Programming Language, 4th Edition" von Bjarne Stroustrup diskutiert. Be S. schrieb: > Ich möchte, dass Objekte von Klassen gespeichert > werden können, die keinen default-Konstruktor besitzen. Das kann std::vector. Be S. schrieb: > Ist das alles in C++ so zulässig oder führt das zu undefiniertem > Verhalten? Leider nein; Ja. Be S. schrieb: > Schon alleine beim reinterpret_cast im Konstruktor von > vector_base (Zeile 62) bin ich mir nicht sicher reinrepret_cast an sich ist ungefährlich, aber darauf folgende Zugriffe sind problematisch. Es gibt aber ein paar Sonder-Regeln bezüglich char-Pointern, sodass deine Speicherveraltung "an sich" okay ist. Aber: Das push_back ist gefährlich! Du rufst den "operator =" auf einer Instanz von "T" auf, auf der nie ein Konstruktor aufgerufen wurde! Das geht alleine schon mit z.B. std::string schief. Du solltest stattdessen einfach den Konstruktor aufrufen, denn "Element hinzufügen" bedeutet ja praktisch "Konstruktor auf Speicher aufrufen". Dazu kannst du die placment-new-Syntax nutzen:
1 | bool push_back(const T & Value){ |
2 | if(Size_ == MaxSize_){ |
3 | return false; |
4 | }
|
5 | |
6 | new (Data_[Size_]) T (Value); |
7 | ++Size_; |
8 | |
9 | return true; |
10 | }
|
11 | |
12 | |
13 | bool push_back(T && Value){ |
14 | if(Size_ == MaxSize_){ |
15 | return false; |
16 | }
|
17 | |
18 | new (Data_[Size_]) T (std::move(Value)); |
19 | ++Size_; |
20 | |
21 | return true; |
22 | }
|
Um das Umkopieren zu vermeiden, ist es empfehlenswert auch noch emplace_back/front zu implementieren. Den komplizierten Teil hast du aber noch vor dir: Move/Copy Konstruktoren und -Assignment-Operatoren korrekt implementieren. Ohne Exception-Support wird das aber glücklicherweise einfacher als normal. Außerdem brauchst du noch Iteratoren, damit die ganzen Algorithmen aus <algorithm> funktionieren.
Vielen Dank für die Antworten! Dr. Sommer schrieb: > Den dynamischen Speicher kann man auch loswerden indem man einen > Allokator an std::vector übergibt. Eine "richtige" vector > Implementierung ist sehr kompliziert, aber eine gute Übung. Ja, das weiss ich, aber ich möchte in meiner Variante eine Fehlerbehandlung per Rückgabewert (und noch ein paar andere Dinge), weswegen ich std::vector nicht einfach so benutzen kann. Üben ist immer gut, mit Containern hab ich noch nicht so viel Erfahrung. Dr. Sommer schrieb: > Das push_back ist gefährlich! Du rufst den "operator =" auf einer > Instanz von "T" auf, auf der nie ein Konstruktor aufgerufen wurde! Das > geht alleine schon mit z.B. std::string schief. Du solltest stattdessen > einfach den Konstruktor aufrufen, denn "Element hinzufügen" bedeutet ja > praktisch "Konstruktor auf Speicher aufrufen". Dazu kannst du die > placment-new-Syntax nutzen: Das dachte ich mir schon, dass man nicht so naiv programmieren darf. Das ist genau der Punkt, an dem ich nicht weiter wusste und halt mal was hingeschrieben habe, um zu schauen obs funktioniert. placement-new kannte ich noch gar nicht. Hab das mal so übernommen. Neue Ausgabe an std::cout: constructor move constructor destructor destructor Meinst du, dass ich auf dieser Grundlage weiterarbeiten kann? Dr. Sommer schrieb: > new (Data_[Size_]) T (Value); Müsste es nicht
1 | new (Data_ + Size_) T (Value); |
heissen? Dr. Sommer schrieb: > Um das Umkopieren zu vermeiden, ist es empfehlenswert auch noch > emplace_back/front zu implementieren. > > Den komplizierten Teil hast du aber noch vor dir: Move/Copy > Konstruktoren und -Assignment-Operatoren korrekt implementieren. Ohne > Exception-Support wird das aber glücklicherweise einfacher als normal. > Außerdem brauchst du noch Iteratoren, damit die ganzen Algorithmen aus > <algorithm> funktionieren. Das kommt alles noch, aber erstmal will ich, dass das Grundgerüst sauber funktioniert. Ich mach das als Hobby, da hab ich genug Zeit. Muss ja nicht heute fertig sein.
Dr. Sommer schrieb: > Wird z.B. im > Buch "The C++ Programming Language, 4th Edition" von Bjarne Stroustrup > diskutiert. Meinst du damit Kapitel 31.4.1 (Seite 902)?
Soweit ich das sehe, ist dein Vector "lediglich" ein Stack. Boost bietet einen "richtigen" statischen Vector an: boost::container::static_vector. Wie oben bereits gesagt -- du brauchst ein placement new um Objekte abzulegen (push_back). -- den (default) operator= entweder ausschließen oder implementieren.
Be S. schrieb: > Meinst du, dass ich auf dieser Grundlage weiterarbeiten kann? Ja, glaube schon. Be S. schrieb: > Müsste es nichtnew (Data_ + Size_) T (Value);heissen? Oh, stimmt. Be S. schrieb: > Meinst du damit Kapitel 31.4.1 (Seite 902)? Weiß grad nicht mehr genau wo das stand... Ich meine das war auf mehrere Kapitel verteilt, wo damit stückweise einzelne Konzepte erläutert wurden.
Mikro 7. schrieb: > Soweit ich das sehe, ist dein Vector "lediglich" ein Stack. Es ist noch nicht einmal ein Stack. Momentan kann man Werte schreiben und löschen, also eine Art write only memory :) Mikro 7. schrieb: > Boost bietet einen "richtigen" statischen Vector an: > boost::container::static_vector. Danke, das schaue ich mir mal an. Da kann ich sicher was lernen. Mikro 7. schrieb: > -- den (default) operator= entweder ausschließen oder implementieren. Das kommt noch, habe ich der Einfachheit halber noch weggelassen. Dr. Sommer schrieb: > Be S. schrieb: >> Meinst du damit Kapitel 31.4.1 (Seite 902)? > Weiß grad nicht mehr genau wo das stand... Ich meine das war auf mehrere > Kapitel verteilt, wo damit stückweise einzelne Konzepte erläutert > wurden. Passt gut zu Stroustrup... Werde dann mal bei Gelegenheit das Buch durchkämmen.
Be S. schrieb: > Das kommt noch, habe ich der Einfachheit halber noch weggelassen. Genau das ist aber problematisch, weil der standardmäßig ja automatisch hinzugefügt wird. Daher solltest du den mindestens mit "=delete" abschalten, damit der nicht versehentlich mal aufgerufen wird, was zu fiesen Effekten führen kann!
Mikro 7. schrieb: > Boost bietet einen "richtigen" statischen Vector an: > boost::container::static_vector. Dazu braucht man gar kein Boost. Standard-C++ hat sowas unter dem Namen std::array.
:
Bearbeitet durch User
Rolf M. schrieb: > Dazu braucht man gar kein Boost. Standard-C++ hat sowas unter dem Namen > std::array. Könntest du bitte zeigen wie std::array mit Objekten ohne default constructor funktioniert?! Be S. schrieb: > Ich möchte, dass Objekte von Klassen gespeichert > werden können, die keinen default-Konstruktor besitzen.
Mikro 7. schrieb: > Könntest du bitte zeigen wie std::array mit Objekten ohne default > constructor funktioniert?!
1 | #include <array> |
2 | |
3 | struct Foo |
4 | {
|
5 | Foo(int) { } |
6 | };
|
7 | |
8 | int main() |
9 | {
|
10 | std::array<Foo, 3> { 1, 2, 3}; |
11 | }
|
Aber das geht mit std::array nicht:
1 | struct Foo |
2 | {
|
3 | Foo(int) { } |
4 | };
|
5 | |
6 | int main() |
7 | {
|
8 | std::array<Foo, 3> Test{}; /* Soll keine foo-Objekte enthalten */ |
9 | }
|
Rolf M. schrieb: > ... Ok. Ertappt. :-) Es braucht also nicht unbedingt den default constructor. Es reicht wenn das Array zum Zeitpunkt der construction durch braced-init-list vollständig gefüllt werden kann (ggf. mit Dummies). Der Vector funktioniert ohne diese Beschränkung. Wenn man also die Funktionalität des STL Vectors möchte, dann reicht std::array nicht aus (Boost's static_vector aber schon).
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.