Forum: PC-Programmierung C-struct Array effektiv nach C++ konvertieren


von Klaus M. (klaus_me)


Angehängte Dateien:

Lesenswert?

Hallo, ich habe in einem Programm eine "alte" C-Struktur. Da ich die GUI 
inzwischen mit Qt programmiere wäre es wohl sinnvoll, auch in 
Datenstrukturen nach und nach auf C++ umzustellen. Es handelt sich um 
ein Tool für Schlagzeuger.
Eine wichtige Struktur sind die Drum-Pattern. Die Drum-Pattern werden 
aus einer Text-Datei eingelesen und sind im Prinzip MIDI-Noten in 
Textform.

Diese sehen z.B. so aus:
1
P Paradiddle16-2
2
T 21 1
3
C 4 4 28 8 90
4
S 8 S6:4 S6:4 S6:4 S6:4 S6:4 S6:4 S6:4 S6:4 
5
N 0 36 64 
6
N 0 42 64 
7
N 240 42 64 
8
N 240 36 64 
9
N 480 38 100 
10
N 480 42 64 
11
N 720 42 64 
12
N 960 42 64 
13
N 960 36 64 
14
N 1200 42 64 
15
N 1440 38 90 
16
N 1440 42 64 
17
N 1680 42 64 
18
N 1920 36 64 
19
N 1920 42 64 
20
N 2160 42 64 
21
N 2160 36 64 
22
N 2400 38 70 
23
N 2400 42 64 
24
N 2640 42 64 
25
N 2880 43 104R
26
N 3000 38 70l
27
N 3120 47 64r
28
N 3240 47 64r
29
N 3360 38 100L
30
N 3480 48 64r
31
N 3600 38 70l
32
N 3720 38 70l
33
E

Insgesammt sind weit über 1000 Schlagzeug-Pattern in der Textdatei. 
Diese werden beim Programmstart eingelesen und in eine C-Struktur
umgewandelt.
1
// Zusammengestuzt...
2
#define MAX_NAME (32-8)
3
#define MAX_STEPS 64
4
#define MAX_PATTERN 2500
5
6
struct Note {
7
    uint16_t time;
8
    uint8_t note, vel;
9
    uint8_t flags, stick;
10
};
11
12
struct DrumPat {
13
    uint8_t genre, type;
14
    uint8_t num, denum;
15
    uint8_t bpm, max_beats;
16
    uint8_t num_notes, num_step_entries;
17
    // Offset name: 8 bytes
18
    char name[MAX_NAME];
19
    // Offset steps 32 bytes
20
    int8_t steptable[MAX_STEPS];
21
    // Offset notes: 96 bytes
22
    struct Note notes[];    
23
};    
24
25
struct DrumPat *drumpattern[MAX_PATTERN];
26
27
size_t getPatternSize(int32_t num_notes)
28
{
29
    // Calculate size of drum pattern
30
    return (sizeof(struct DrumPat) + (num_notes+1) * sizeof(struct Note));
31
}

Die Einleseroutine berechnet die Größe jeder Struktur (hängt von der 
Anzahl der Noten ab) und alloziert den Speicher per malloc. Dabei sind 
einige Größen fest codiert, was nicht so schön ist.
Nun würde ich gerne das ganze in einen sinnvollen C++ Container 
überführen, also eine Art std::list in der dann die einzelnen Elemente
jeweils ein Schlagzeug Pattern darstellt, wobei dort dann sowohl der 
Name, StepTable und Noten dynamische Arrays sind. Wobei zum bloßen 
speichern würde auch ein std::array gehen und kein std::vector. Bisher 
ist es so, wenn ich ein pattern verändere, alloziere ich Speicher für 
ein neues Pattern und geben dann den alten Speicher frei und weise dem 
passenden Eintrag der Zeiger *drumpattern[n]; die neue Adresse zu.

Nun kann ich einigermaßen C aber nur wenig C++. Was wäre hier eine 
sinnvolle Vorgehensweise?

: Bearbeitet durch User
von Udo K. (udok)


Lesenswert?

Klaus M. schrieb:
> Nun kann ich einigermaßen C aber nur wenig C++. Was wäre hier eine
> sinnvolle Vorgehensweise?

Sinnvoll ist es, das so zu lassen wie es ist, und die Zeit in Features 
zu stecken von denen der Anwender was hat.

Wenn dir langweilig ist und du unübersichtliche Syntax magst, dann 
kannst du natürlich sowas hinschreiben wie:
1
std::vector<DrumPat> drumpattern;
2
3
struct DrumPat {
4
    uint8_t genre, type;
5
    uint8_t num, denum;
6
    uint8_t bpm, max_beats;
7
    uint8_t num_notes, num_step_entries;
8
    std::string name;
9
    std::vector<int8_t> steptable;
10
    std::vector<Note> notes;    
11
};

Für die Laufzeit und den Speicherverbrauch wird es keinen merkbaren 
Unterschied machen.

: Bearbeitet durch User
von Frank D. (Firma: LAPD) (frank_s634)


Lesenswert?

Klaus M. schrieb:
> Nun kann ich einigermaßen C aber nur wenig C++. Was wäre hier eine
> sinnvolle Vorgehensweise?

Es so zu lassen wie es ist.

von Klaus M. (klaus_me)


Lesenswert?

Das ist alles? Hintergrund: Ich habe mit doch etliche bugs gekämpft, mal 
ein Indizes um eines zu hoch uns schon schlägt der AdressSanitizer zu.
Der hat auch Bugs gefunden, die mir noch gar nicht aufgefallen sind ;-)
Auch in die Beschränkung auf 26 Zeichen beim Namen inzwischen nervig.
Viele Pattern haben nur 4-8 Einträge bei der StepTable, bei manchen 
reichen aber die 64 nicht...

Die Felder "uint8_t num_notes, num_step_entries" bräuchte ich auch nicht 
mehr, da ich diese Infos über pat.notes.size() bekomme, oder?

Ich mach dann nur noch ein "new struct DrumPat pat" und C++ kümmert sich 
um die dynamischen Allozierung von std::string name, std::vector 
steptable und std:vector notes?

Und dann einfach beim einlesen ein
1
std::vector<DrumPat> drumpattern;
2
...
3
while (drumpatterin in textfile)
4
{
5
  new struct DrumPat pat; // New -> malloc
6
  // pat befüllen
7
  pat.genre = genre;
8
  pat.type = type;
9
  ...
10
  while (steps in patternfile)
11
    pat.steptable.push_back(step);
12
  while (notes in patternfile)
13
    pat.notes.push_back(note);
14
  ...
15
}
16
drumpattern.push_back(pat);

Zum zeichnen der Noten dann
1
std::list<DrumPat>::iterator actpat;
2
std::list<Note>::iterator note;
3
// actpat auf das aktuell zu zeichnende pattern legen
4
...
5
for (std::vector<Note> note 
6
for (note : actpat.notes)
7
{
8
  draw_note(note.time, note.note, note.vel, ...)
9
}

Frank D. schrieb:
> Es so zu lassen wie es ist.

Ok, ich dachte nur, die C-Struktur ist etwas tricky, C++ wäre 
"sauberer".
Vom Speicher dürfte ich nicht viel gewinnen, da ja ein std::vector ein 
paar Zeiger speichert, jeder Zeiger sind 8 Bytes auf einem 64 Bit 
System.

Also eher C lassen? Hätte auch den Vorteil, sollte ich das Tool doch mal 
wieder auf einem embedded ARM portieren, dürfte das (etwas) einfacher 
sein.
Meine größten Probleme könnte ich mit Zeigern lösen, in plain C:
1
struct DrumPatX {
2
    uint8_t genre, type;
3
    uint8_t num, denum;
4
    uint8_t bpm, max_beats;
5
    uint8_t num_notes, num_step_entries;
6
    char *name;
7
    int8_t *steptable;
8
    // offset notes: 24 bytes on 64-bit-systems
9
    // offset notes: 16 bytes on 32-bit-systems
10
    struct Note notes[];    
11
};

Eine Sortierfunktion sollte auch in C-Only gut machbar sein, da ich
ja direkt die Zeiger
1
struct DrumPat *drumpattern[MAX_PATTERN];
manipulieren kann.

von Hans-Georg L. (h-g-l)


Lesenswert?

Klaus M. schrieb:
> Hallo, ich habe in einem Programm eine "alte" C-Struktur.
snip

> Bisher ist es so, wenn ich ein pattern verändere, alloziere ich Speicher
> für ein neues Pattern und geben dann den alten Speicher frei
> Nun kann ich einigermaßen C aber nur wenig C++. Was wäre hier eine
> sinnvolle Vorgehensweise?

Sobald du deine C struct mit einem C++ Compiler übersetzt verhält sie 
sich wie eine Klasse. Hat also Konstruktoren und Destruktor. Der einzig 
wichtige Unterschied zu einer Klasse ist, das bei der struct alles 
public ist
im Gegensatz zu einer Klasse da ist alles private. Da könntest du zum 
Beispiel deine Parameter einem Konstruktor übergeben und mit new und 
delete deine Struktur anlegen und löschen. Du könntest auch eine Methode 
play_note(uint98_t note)implementieren usw. Ich denke das würde schon 
einiges
vereinfachen ...

von Oliver S. (oliverso)


Lesenswert?

Prinzipiell hast du schon recht. Ein sauberes C++-Programm sollte kein 
new uns schon gar kein malloc enthalten. Das überlässt man alles den 
Containern.

Der richtige Ansatz ist über std::vector, wie oben schon gezeigt.

Oliver

von Udo K. (udok)


Lesenswert?

Klaus M. schrieb:
> Die Felder "uint8_t num_notes, num_step_entries" bräuchte ich auch nicht
> mehr, da ich diese Infos über pat.notes.size() bekomme, oder?
>
> Ich mach dann nur noch ein "new struct DrumPat pat" und C++ kümmert sich
> um die dynamischen Allozierung von std::string name, std::vector
> steptable und std:vector notes?
> ...

Wenn du "new std::vector<DrumPat\*>" wie in deinem Beispiel verwendest, 
hast du keinen Vorteil, ausser das jetzt malloc "new" heisst und du nur 
3 Buchstaben tippen musst (aber delete gleicht das wieder aus).
Ich habe nicht umsonst std::vector<DrumPat> (ohne\*) vorgeschlagen. 
Damit kümmert sich der std::vector um die Speicherverwaltung.  Du 
brauchst aber noch einen Konstruktor DrumPat::DrumPat(), einen 
Destruktor DrumPat::~DrumPat(), einen Kopy Konstruktor 
DrumPat::DrumPat(const DrumPat&), und neuerdings einen Move Konstruktor 
operator=(DrumPat&&) -- du kannst dich aber eventuell auch auf die 
automatisch vom Compiler generierten Funktionen verlassen.  Und plane 
ein paar Wochen zum Verplempern ein, um rauszufinden wann diese 
Funktionen aufgerufen werden, und was genau der feine Unterschied 
zwischen & und && ist, und ob nun Referenzen besser sind als Zeiger... 
und warum die Fehlermeldungen plötzlich 5 Zeilen lang sind, und achte 
genau auf das "const", das macht noch einen feinen Unterschied.  In C++ 
gibt es X Wege zum Ziel, wo es in C genau einen Weg gibt.  Diese 
Entscheidungen zu treffen kostet dem Gelegenheitsprogrammierer viel 
wertvolle Zeit, wie man schön an deiner ersten Frage hier sieht.

von Oliver S. (oliverso)


Lesenswert?

Udo K. schrieb:
> Wenn du "new std::vector<DrumPat\*>" wie in deinem Beispiel verwendest,
> hast du keinen Vorteil, ausser das jetzt malloc "new" heisst und du nur
> 3 Buchstaben tippen musst (aber delete gleicht das wieder aus).
> Ich habe nicht umsonst std::vector<DrumPat> (ohne\*) vorgeschlagen.
> Damit kümmert sich der std::vector um die Speicherverwaltung.  Du
> brauchst aber noch einen Konstruktor DrumPat::DrumPat(), einen
> Destruktor DrumPat::~DrumPat(), einen Kopy Konstruktor
> DrumPat::DrumPat(const DrumPat&), und neuerdings einen Move Konstruktor
> operator=(DrumPat&&) -- du kannst dich aber eventuell auch auf die
> automatisch vom Compiler generierten Funktionen verlassen.

Zum Glück legt der Compiler bei einfachen structs die benötigten 
Operatoren als default an. Die Konstrktoren bekommt man dazu sicher hin.

Udo K. schrieb:
> std::vector<Note> notes;

std::string sollte da besser geeignet sein.

Oliver

: Bearbeitet durch User
Beitrag #7907705 wurde vom Autor gelöscht.
von Bruno V. (bruno_v)


Lesenswert?

Am Ende kocht C++ auch nur mit Wasser. Wenn Du die Speicherverwaltung 
C++ überlässt, hast Du auf einem PC vermutlich die geringsten Probleme, 
weil Speicher keine Rolle spielt und Du Dich um nichts kümmern musst. 
Selbst Speicherlecks sind kein Problem, solange sie nur durch 
Benutzer-Interaktion entstehen.

Prinzipiell kannst Du auch in C alle Elemente dynamisch machen. Mit ein 
paar Zugriffs-Funktionen ist es ähnlich wie in C++.

Es macht nur auf einem PC mit unlimitiertem Speicher keinen Sinn.

Sinnvoll wäre das nur, wenn Du in einem Stück Ram zu Beginn möglichst 
viele Strukturen total unterschiedlicher Größe einlesen möchtest und zur 
Laufzeit nicht wieder freigibst. Oder direkt im (limitierten) ROM.

von Oliver S. (oliverso)


Lesenswert?

Bruno V. schrieb:
> Am Ende kocht C++ auch nur mit Wasser. Wenn Du die
> Speicherverwaltung
> C++ überlässt, hast Du auf einem PC vermutlich die geringsten Probleme,
> weil Speicher keine Rolle spielt und Du Dich um nichts kümmern musst.

Die Structs sind in C++ genauso groß wie in C, und das bisschen 
Verwaltung für den Vektor spielt kaum eine Rolle.


> Selbst Speicherlecks sind kein Problem, solange sie nur durch
> Benutzer-Interaktion entstehen.

Der Vorteil der Standard Container ist, daß du damit keine Speicherlecks 
bekommst.


>
> Prinzipiell kannst Du auch in C alle Elemente dynamisch machen. Mit ein
> paar Zugriffs-Funktionen ist es ähnlich wie in C++.
>
> Es macht nur auf einem PC mit unlimitiertem Speicher keinen Sinn.

Es macht darauf ganz genauso viel Sinn wie auf Rechnern mit limitiertem 
Speicher.

Oliver

von Udo K. (udok)


Lesenswert?

Oliver S. schrieb:
> Die Structs sind in C++ genauso groß wie in C, und das bisschen
> Verwaltung für den Vektor spielt kaum eine Rolle.
Nein, sie sind grösser.  Er braucht ja zwei std::vector (etwa 32 Bytes 
pro vector) und einen std::string (auch etwa 32 Bytes) für sein struct 
DrumPat.
Dazu allokiert jeder vector und string intern Bufferspeicher damit er 
nicht für jeden Furz ein malloc machen muss.

>> Benutzer-Interaktion entstehen.
>
> Der Vorteil der Standard Container ist, daß du damit keine Speicherlecks
> bekommst.

Der TE hat in seinem C++ Beispiel mit std::vector schon ein Speicherleak 
drinnen...  wenn man C++ nicht wirklich versteht ist die Gefahr für 
Fehler gross.  C++ mit den ganzen Libs dazu ist ziemlich komplex.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Udo K. schrieb:
> C++ mit den ganzen Libs dazu ist ziemlich komplex.

Noch viel komplexer ist handgedenglter C-Code, der die gleiche 
Funktionalität nachbilden will.

Wenn man sich allerdings an

Oliver S. schrieb:
> hast du schon recht. Ein sauberes C++-Programm sollte
> kein
> new uns schon gar kein malloc enthalten. Das überlässt man alles den
> Containern.

hält, klappt es auch ohne Memory leaks.

Oliver

von Daniel A. (daniel-a)


Lesenswert?

Udo K. schrieb:
> Ich habe nicht umsonst std::vector<DrumPat> (ohne\*) vorgeschlagen.
> Damit kümmert sich der std::vector um die Speicherverwaltung.

Wir wissen ja nicht, ob es noch weitere Pointer auf das DrumPat gibt. Da 
wäre es dann unpraktisch, wenn std::vector die Instanzen verschiebt.

: Bearbeitet durch User
von Klaus M. (klaus_me)


Lesenswert?

Udo K. schrieb:
> Der TE hat in seinem C++ Beispiel mit std::vector schon ein Speicherleak
> drinnen...

Du meinst das "new struct DrumPat pat;", oder?
Wir war (noch) nicht klar, dass push_back() eine Kopie anlegt und
der Speicher dazu automatisch alloziert wird. Wobei das bei wenig 
nachdenken ja logisch ist, ist ja bei der steptable und notes auch
so. Es reicht also nur eine struct auf dem Stack die immer wieder
neu befüllt wird.
1
std::vector<DrumPat> drumpattern;
2
struct DrumPat pat;
3
...
4
while (drumpattern in textfile)
5
{
6
  // pat befüllen
7
8
  pat.genre = genre;
9
  pat.type = type;
10
  ...
11
  while (steps in patternfile)
12
    pat.steptable.push_back(step);
13
  while (notes in patternfile)
14
    pat.notes.push_back(note);
15
  ...
16
  drumpattern.push_back(pat);
17
}

von Udo K. (udok)


Lesenswert?

Ja, das meinte ich. Nur so geht es auch nicht, du musst das "pat" ja 
nach dem push_back() leeren.  Also die Definition von "pat" an den 
Anfang der Schleife, oder ein drumpattern.push_back() am Anfang machen, 
und das neue Element auffüllen, spart einen CopyConstructur Aufruf, 
kostet aber im Fehlerfall. Und den Rat von Daniel beherzigen:  Nur mit 
indizes auf die Elemente zugreifen, da der std::vector die Elemente im 
Speicher verschieben kann.  Du siehst, C++ ist kein Wundermittel.

von Oliver S. (oliverso)


Lesenswert?

Udo K. schrieb:
> oder ein drumpattern.push_back() am Anfang machen, und das neue Element
> auffüllen, spart einen CopyConstructur Aufruf

Oder noch besser dem struct einen passenden Konstruktor spendieren, und 
dann im Vector per emplace_back in place erzeugen.

Oliver

von Udo K. (udok)


Lesenswert?

Welches emplace_back(), das von MSVC oder das neuere C++0x?  Oder doch 
ein push_back(Type&& val) mit dem "&&" verwenden?  Keine Ahnung, aber 
alles recht komplizert für den Gelegenheitsprogrammierer.

: Bearbeitet durch User
von Udo K. (udok)


Lesenswert?

Ich habe das mit emplace_back ausprobiert:
1
#include <stdio.h>
2
#include <vector>
3
#include <string>
4
#include <cstdint>
5
6
7
struct Note
8
{
9
    uint16_t time;
10
    uint8_t note, vel;
11
    uint8_t flags, stick;
12
};
13
14
15
struct DrumPat
16
{
17
    int x;
18
    uint8_t genre, type;
19
    uint8_t num, denum;
20
    uint8_t bpm, max_beats;
21
    uint8_t num_notes, num_step_entries;
22
    std::string name;
23
    std::vector<int8_t> steptable;
24
    std::vector<Note> notes;
25
26
    DrumPat()                               { x = 0; printf("%p DrumPat()\n", this); }
27
    DrumPat(int x_arg) : x(x_arg)           { printf("%p DrumPat(x_arg)\n", this); }
28
    DrumPat(const DrumPat &rhs) noexcept    { x = rhs.x; printf("%p DrumPat(DrumPat &)\n", this); }
29
    DrumPat(DrumPat &&rhs) noexcept         { x = rhs.x; printf("%p DrumPat(DrumPat &&)\n", this); }
30
    ~DrumPat()                              { printf("%p x=%d ~DrumPat()\n", this, x); }
31
};
32
33
34
struct GlobalData
35
{
36
    std::vector<DrumPat>    drumpattern;
37
    GlobalData()            { printf("%p GlobalData()\n", this); }
38
    ~GlobalData()           { printf("%p ~GlobalData()\n", this); }
39
};
40
41
GlobalData g_data;
42
43
44
int main ()
45
{
46
    printf("call main\n");
47
48
    {
49
        printf("--\ncall emplace_back:\n");
50
        g_data.drumpattern.emplace_back(0);
51
        DrumPat &r = g_data.drumpattern.back();
52
        printf("leaving scope (r.x=%d)\n", r.x);
53
    }
54
55
    {
56
        printf("--\ncall push_back:\n");
57
        g_data.drumpattern.push_back(1);
58
        DrumPat &r = g_data.drumpattern.back();
59
        printf("leaving scope (r.x=%d)\n", r.x);
60
    }
61
62
    printf("--\nleaving main\n");
63
    return 0;
64
}

Ausgabe:
1
00007FF799282BE8 GlobalData()
2
call main
3
--
4
call emplace_back:
5
00000237C8292830 DrumPat(x_arg)
6
leaving scope (r.x=0)
7
--
8
call push_back:
9
00000054450FFE10 DrumPat(x_arg)
10
00000237C8292900 DrumPat(DrumPat &&)
11
00000237C82928A0 DrumPat(DrumPat &&)
12
00000237C8292830 x=0 ~DrumPat()
13
00000054450FFE10 x=1 ~DrumPat()
14
leaving scope (r.x=1)
15
--
16
leaving main
17
00007FF799282BE8 ~GlobalData()
18
00000237C82928A0 x=0 ~DrumPat()
19
00000237C8292900 x=1 ~DrumPat()

push_back erzeugt echt zwei Kopien um ein Objekt an einen Vektor 
dranzuhängen.  Und das trotz der zig Zeilen Tipperei und dem DrumPat(&&) 
Move Konstruktor.  Bisher hatte ich immer push_back verwendet.  Das 
meinte ich mit C++ ist nichts für Gelegenheitsprogrammierer wie mich. 
Der Dreck ist immer für Überraschungen gut.

von Oliver S. (oliverso)


Lesenswert?

Udo K. schrieb:
> Welches emplace_back(), das von MSVC oder das neuere C++0x?

"Neueres C++0x" ROTFL

Wir haben 2025. emplace_back gibt's seit C++17.

Man kann sich natürlich mit dem Ursprungs-C++ von Stroustrup abmühen, 
aber wer heute einsteigt, darf und sollte schon modernes C++ verwenden.

Oliver

von Udo K. (udok)


Lesenswert?

Oliver S. schrieb:
> Wir haben 2025. emplace_back gibt's seit C++17.

Nutzt nur nichts, wenn der hier unterstützte Compiler erst bei C++11 
ist...
Aber ich habe wieder was gelernt.

: Bearbeitet durch User
von Roger S. (edge)


Lesenswert?

Udo K. schrieb:
> push_back erzeugt echt zwei Kopien um ein Objekt an einen Vektor
> dranzuhängen.
1
GlobalData() { printf("%p GlobalData()\n", this); drumpattern.reserve(2); }

von Udo K. (udok)


Lesenswert?

Danke. drumpattern.reserve(2) hilft, es reduziert die Anzal der 
temporären Kopien von 2 auf 1.  Man muss halt die Anzahl der Elemente 
wissen.

von Daniel A. (daniel-a)


Lesenswert?

Ich denke push_back ist schon in Ordnung. Macht euch das Leben einfach. 
Optimieren kann man später immer noch.

Ausserdem, ob man jetzt f((mystruct){1,2,3}) oder f(1,2,3) hat, kommt 
doch genau aus selbe raus, da kommen die genau gleichen Daten auf den 
Stack. Ob man jetzt push_back oder emplace_back nutzt, am Schluss werden 
da doch genau gleich viel Daten kopiert, gerade bei einem simplen 
Struct.
Reserve hilft sicher auch weniger als man meinen würde, die Vektoren 
werde sicher nicht jedes mal neuen Speicher reservieren, wenn ein 
Element dazu kommt. Bei meinen C Projekten nutze ich z.B. immer die 
Speicherverdoppelungsstrategie, C++ wird sicher was ähnliches machen.

von Udo K. (udok)


Lesenswert?

Ich habe mir das noch genauer angeschaut. Der Unterschied zwischen 
push_back und emplace_back ist kein allzu grosser.

Wenn ich einen Vektor mit 1000 Elementen mit push_back() anlege, dann 
werden insgesammt 3137 temporäre Elemente erzeugt.

Wenn ich emplace_back() verwende, dann werden 2137 temporäre Elemente 
erzeugt.

Die Tests laufen auf Win 11 mit einem aktuellen MSVC.

Anscheinend verwendet der std::vector intern kein realloc sondern 
malloc/free.   Wenn der vector keinen Platz hat, wird intern ein neuer 
Vektor erzeugt, alle Objekte werden in den neuen Vektor kopiert, und der 
alte wird zerstört.
Der neue Vektor hat etwas Reserve, damit der Algorithmus nicht zu N^2 
ausartet.

Hier nochmal der Code:
1
#include <stdio.h>
2
#include <stdint.h>
3
#include <vector>
4
#include <string>
5
6
7
struct Note
8
{
9
    uint16_t time;
10
    uint8_t note, vel;
11
    uint8_t flags, stick;
12
};
13
14
15
struct DrumPat
16
{
17
    int x;
18
    uint8_t genre, type;
19
    uint8_t num, denum;
20
    uint8_t bpm, max_beats;
21
    uint8_t num_notes, num_step_entries;
22
    std::string name;
23
    std::vector<int8_t> steptable;
24
    std::vector<Note> notes;
25
26
    DrumPat()                               { x = 0; printf("%p DrumPat()\n", this); }
27
    DrumPat(int arg) : x(arg)               { printf("%p DrumPat(arg)\n", this); }
28
    DrumPat(const DrumPat &rhs) noexcept    { x = rhs.x; printf("%p DrumPat(DrumPat &)\n", this); }
29
    DrumPat(DrumPat &&rhs) noexcept         { x = rhs.x; printf("%p DrumPat(DrumPat &&)\n", this); }
30
    ~DrumPat();
31
};
32
33
34
struct GlobalData
35
{
36
    std::vector<DrumPat>    drumpattern;
37
38
    GlobalData()
39
    {
40
            destr_count=0;
41
            printf("%p GlobalData()\n", this);
42
            /*drumpattern.reserve(2);*/
43
    }
44
45
    ~GlobalData()
46
    {
47
        printf("%p ~GlobalData() Temp-Elements=%d\n", this, destr_count);
48
    }
49
50
    int destr_count;
51
};
52
53
54
GlobalData g_data;
55
56
57
DrumPat::~DrumPat()
58
{
59
    g_data.destr_count++;
60
    printf("%p %d ~DrumPat()\n", this, x);
61
}
62
63
64
int main(int argc, char* argv[])
65
{
66
    int count = 0;
67
    int use_emplace = 0;
68
69
    if (argc > 1)
70
        count = atoi(argv[1]);
71
72
    if (argc > 2)
73
        use_emplace = atoi(argv[2]) ? 1 : 0;
74
75
    if (count <= 0)
76
        count = 1;
77
78
    printf("call main count=%d use_emplace=%d\n", count, use_emplace);
79
80
    for (int i = 0; i < count; i++)
81
    {
82
        if (use_emplace)
83
        {
84
            printf("-- %d\ncall emplace_back:\n", i);
85
            g_data.drumpattern.emplace_back(i);
86
            DrumPat &r = g_data.drumpattern.back();
87
            printf("leaving scope\n");
88
        }
89
        else
90
        {
91
            printf("-- %d\ncall push_back:\n", i);
92
            g_data.drumpattern.push_back(i);
93
            DrumPat &r = g_data.drumpattern.back();
94
            printf("leaving scope\n");
95
        }
96
    }
97
98
    printf("--\nleaving main, size=%d\n", (int)g_data.drumpattern.size());
99
    return 0;
100
}

von Oliver S. (oliverso)


Lesenswert?

Udo K. schrieb:
> Wenn der vector keinen Platz hat, wird intern ein neuer Vektor
> erzeugt, alle Objekte werden in den neuen Vektor kopiert, und der alte
> wird zerstört.

Das ist so. Wenn man aber weiss, dass der so wächst, kann man auch 
gleich ausreichend Speicher reservieren.

Udo K. schrieb:
> Nutzt nur nichts, wenn der hier unterstützte Compiler erst bei C++11
> ist.

Wenns nicht irgendwelche Firmenrichtlinien Compilerversionen oder 
Sprachstandards vorgegeben, wie es hier ja nicht der Fall ist, gibt es 
gar keinen Grund, prähistorische Compiler zu verwenden.

Wer heute, wie der TO, mit C++ anfängt, sollte nicht unter C++17 
einsteigen.

Oliver

: Bearbeitet durch User
von Udo K. (udok)


Lesenswert?

Schon klar, aber warum verwendet der std:vector nicht realloc?

Kann ich das noexcept im Konstruktor eigentlich weglassen, oder hat das 
einen Nutzen?

von Roger S. (edge)


Lesenswert?

Udo K. schrieb:
> Schon klar, aber warum verwendet der std:vector nicht realloc?

Das wuerde nur funktionieren wenn garantiert waere dass realloc den 
bestehenden Speicher erweitert - somit die bestehenden Objekte am selben 
Platz bleiben.

von Udo K. (udok)


Lesenswert?

Warum muss das Object am selben Platz bleiben?  Es wird ja gerade ein 
Objekt drangehängt, und da darf der Vektor ja die Addressen verschieben? 
Externe Zeiger werden auf jeden Fall ungültig.

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

Ich habe schon lange nichts mehr mit C++ gemacht, kann es also nicht mit 
Sicherheit sagen.

Ich nehme mal an, dass das Objekt dafür trivial kopierbar oder trivial 
movable sein muss. Eine Implementation könnte das mit 
is_trivially_copyable respektive is_trivially_move_constructible prüfen. 
Das wiederum checkt ob es den default copy/move Konstruktor nutzt.

von Bruno V. (bruno_v)


Lesenswert?

Oliver S. schrieb:
> Das ist so. Wenn man aber weiss, dass der so wächst, kann man auch
> gleich ausreichend Speicher reservieren.

Das war genau mein Argument.

Nur wenn der Speicher knapp ist, kannst Du mit relativ kleiner 
Eigenverwaltung x kByte reservieren und die Strukturen dynamisch 
anlegen. Bei 1000 Elementen mit im Mittel 150 Byte Nutzdaten reichen 
dann ein 200kByte Speicherblock. Natürlich mit Platz am Ende um 
Elemente, die größer werden, neu dort hinzulegen. Und wenn der Speicher 
voll ist, fräst Du einmal mit memmove dadurch und alle liegen wieder 
back to back.

Wenn ich statt 200kByte 2MB habe, ist das Blödsinn. Aber nicht, wenn in 
bestehender HW mit C++-Mitteln nur 500 Elemente Platz finden und es 
plötzlich 2000 werden sollen.

von Oliver S. (oliverso)


Lesenswert?

Udo K. schrieb:
> Es wird ja gerade ein Objekt drangehängt, und da darf der Vektor ja die
> Addressen verschieben? Externe Zeiger werden auf jeden Fall ungültig.

Nicht auf jeden Fall, sondern nur, wenn eine reallocation stattfindet.

Oliver

von Klaus M. (klaus_me)


Lesenswert?

Wenn ich hier so mitlese, schwindet meine Motivation, das ganze auf 
std::vector umzustellen, zumal ich mit dem ändern von drei Defines 
problemlos den Speicher aufblähen kann, aber halt erstmal nicht 
dynamisch.

Ich bilde mir ein, dass ich in C relativ gut verstehe, was der Compiler 
macht (va. bei der Speicherverwaltung).

Da ich bei den Pattern schon eine dynamische Speicherverwaltung 
(malloc/free) habe müsste ich eigentlich nur den festen Speicher für die 
Namen und die StepTable erhöhen. Vermutlich kostet mich das weniger 
Speicher als wenn ich einmal std::string und std::vector einfüge.

Die Anzahl der Noten ist schon dynamsich und m.E. sehr platzsparend. Der 
größte Vorteil, den ich mir erhoffe von std::vector ist halt keine 
Buffer-Überläufe mehr und keine Memory-Leaks.

Oliver S. schrieb:
> Udo K. schrieb:
>> std::vector<Note> notes;
>
> std::string sollte da besser geeignet sein.

Wieso das denn?

Oliver S. schrieb:
> Udo K. schrieb:
>> Es wird ja gerade ein Objekt drangehängt, und da darf der Vektor ja die
>> Addressen verschieben? Externe Zeiger werden auf jeden Fall ungültig.
>
> Nicht auf jeden Fall, sondern nur, wenn eine reallocation stattfindet.

Ich kopiere vor dem Bearbeiten das Pattern in einen Bearbeitungspuffer, 
von dort wird dann alles weitere gemacht (Manipulation und auch das 
Abspielen).

von Oliver S. (oliverso)


Lesenswert?

Klaus M. schrieb:
> Oliver S. schrieb:
>> Udo K. schrieb:
>>> std::vector<Note> notes;
>>
>> std::string sollte da besser geeignet sein.
>
> Wieso das denn?

Ist Blödsinn. Ich hatte was anderes gelesen, als da steht. Vergiss das 
mit dem string.

Oliver

von Oliver S. (oliverso)


Lesenswert?

Klaus M. schrieb:
> Der größte Vorteil, den ich mir erhoffe von std::vector ist halt keine
> Buffer-Überläufe mehr und keine Memory-Leaks.

Und genau das bekommst du auch.

Oliver

von Klaus M. (klaus_me)


Lesenswert?

Udo K. schrieb:
> Kann ich das noexcept im Konstruktor eigentlich weglassen, oder hat das
> einen Nutzen?

Das habe ich mich auch gefragt. Anscheinend kann man hier auch lange 
diskutieren, ob das nun Sinn macht.

https://stackoverflow.com/questions/10787766/when-should-i-really-use-noexcept

von Oliver S. (oliverso)


Lesenswert?

Klaus M. schrieb:
> Udo K. schrieb:
>> Kann ich das noexcept im Konstruktor eigentlich weglassen, oder hat das
>> einen Nutzen?
>
> Das habe ich mich auch gefragt. Anscheinend kann man hier auch lange
> diskutieren, ob das nun Sinn macht.
> https://stackoverflow.com/questions/10787766/when-should-i-really-use-noexcept

Ob das Sinn macht, muss man nicht diskutieren.

Deklariert du eine Funktion als noexept, geht der Compiler davon aus, 
dass die keine Exception wirft, und kann entsprechend optimieren. Zudem 
lassen sich einer noexept Funktion auch nur noecept Funktionen aufrufen.

Kommt dann trotzdem eine exception, Crasht das Programm halt, bzw. ruft 
std::terminate auf.

Oliver

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Zudem lassen sich einer noexept Funktion auch nur noecept
> Funktionen aufrufen.

Sicher?

Angenommen eine Funktion A, die exceptions werfen kann (also ohne 
noexcept) und eine Funktion B, die A aufruft, jedoch mit solchen 
Parametern, dass A immer ohne Exception läuft.  In dem Fall kann B doch 
noexcept sein?

von Oliver S. (oliverso)


Lesenswert?

Johann L. schrieb:
> Oliver S. schrieb:
>> Zudem lassen sich einer noexept Funktion auch nur noecept
>> Funktionen aufrufen.
>
> Sicher?
>
> Angenommen eine Funktion A, die exceptions werfen kann (also ohne
> noexcept) und eine Funktion B, die A aufruft, jedoch mit solchen
> Parametern, dass A immer ohne Exception läuft.  In dem Fall kann B doch
> noexcept sein?

Ok, ich habs zu strikt formuliert. Aufrufen kann man, was immer man 
möchte. Ein throw aus einer noexcept Funktion heraus, egal, ob direkt 
oder indirekt, führt halt zu terminate.

Oliver

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Der Punkt ist: Wenn eine Funktion überhaupt keine Exception werfen kann, 
dann kann man noexcept verwenden.  Der Compiler kann das i.d.R. nicht 
erkennen.

von Oliver S. (oliverso)


Lesenswert?

Johann L. schrieb:
> Der Punkt ist: Wenn eine Funktion überhaupt keine Exception werfen
> kann,
> dann kann man noexcept verwenden.  Der Compiler kann das i.d.R. nicht
> erkennen.

Man kann auch noexept verwenden, wenn eine Funktion Exceptions wirft. 
Führt dann halt zu terminate/abort. Das kann ja durchaus mal gewollt 
sein.

Oliver

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Johann L. schrieb:
>> Der Punkt ist: Wenn eine Funktion überhaupt keine Exception werfen
>> kann,
>> dann kann man noexcept verwenden.  Der Compiler kann das i.d.R. nicht
>> erkennen.
>
> Man kann auch noexept verwenden, wenn eine Funktion Exceptions wirft.

Kann man, aber nicht ohne das Verhalten des Programms zu ändern.

Im von mir genannten Fall wird der Code effizienter, und das Verhalten 
ändert sich nicht.

von Oliver S. (oliverso)


Lesenswert?


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.