Forum: Compiler & IDEs [C++] vector für Mikrocontroller


von B. S. (bestucki)


Angehängte Dateien:

Lesenswert?

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
von Di P. (drpepper) Benutzerseite


Lesenswert?

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

von Dr. Sommer (Gast)


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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)?

von Mikro 7. (mikro77)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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!

von Rolf M. (rmagnus)


Lesenswert?

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
von Mikro 7. (mikro77)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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
}

von B. S. (bestucki)


Lesenswert?

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
}

von Mikro 7. (mikro77)


Lesenswert?

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
Noch kein Account? Hier anmelden.