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
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
boolpush_back(constT&Value){
2
if(Size_==MaxSize_){
3
returnfalse;
4
}
5
6
new(Data_[Size_])T(Value);
7
++Size_;
8
9
returntrue;
10
}
11
12
13
boolpush_back(T&&Value){
14
if(Size_==MaxSize_){
15
returnfalse;
16
}
17
18
new(Data_[Size_])T(std::move(Value));
19
++Size_;
20
21
returntrue;
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.
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.
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).