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.
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?
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
structDrumPat{
4
uint8_tgenre,type;
5
uint8_tnum,denum;
6
uint8_tbpm,max_beats;
7
uint8_tnum_notes,num_step_entries;
8
std::stringname;
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.
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
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 ...
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
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.
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
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.
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
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.
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
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.
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.
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.
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
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.
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.
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
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.
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.
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:
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
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.
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.
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.
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.
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
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).
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
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
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
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?
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
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.
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
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.