Forum: PC-Programmierung C++/placement new: irgendein Grund warum std::string problematisch sein sollte?


von cppbert (Gast)


Lesenswert?

Ich hab hier (fremden) Code der mit placement_new std::strings in einen 
gemeinsamen Puffer steckt - es ist klar das nur die Member des Strings 
selber in dem Placement new buffer landen - der Rest (wenn der String zu 
gross wird) landet auf dem Heap

jetzt meldet mir ein Unit-Test + ASAN im Dunstkreis um eine Zuweisung 
das hier - unter Ubuntu 21.10 x64/Clang 13.x
1
==11194==ERROR: LeakSanitizer: detected memory leaks

wenn eine Zuweisung-Zeile aktiv ist
und auch nur wenn ich nicht mit einem const char* zuweise
1
std::string& ziel bezieht sich auf einen std::string irgendwo im placement new buffer
2
3
1.
4
ziel = "blub"; // keine Meldung
5
ziel.assign("blub"); // keine Meldung
6
ziel = std:string("blub"); // keine Meldung
7
8
2.
9
ziel.assign( chars, chars + char_count ); // Leak
10
11
3.
12
ziel = std::string(chars, chars + char_count); // Leak

mein Kollege meinte das placement new und std::string nicht kompatible 
sind
was für mich ein Ammenmärchen aus den Zeiten von Drachen und Rittern ist

ich denke irgendwie ist die Quelle "beschädigt" oder sowas

mein Test-Code der erstmal zeigt das placement new auf std::string nicht 
per se ein Problem ist
1
#include <string>
2
3
int main()
4
{
5
  {
6
    std::string a;
7
    
8
    a = "hallo";
9
    a = "bert";
10
  }
11
12
  {  
13
    std::string* b = new std::string();
14
    b->operator=("hallo");
15
    b->operator=("bert");
16
    delete b;
17
  }
18
  
19
  {
20
    char buffer[sizeof(std::string)]{};
21
22
    std::string* c = new (buffer) std::string();
23
   
24
    c->operator=("hallo");
25
    c->operator=("bert");
26
    c->assign(std::string("uwe"));
27
28
    c->~basic_string();
29
  }
30
31
  {
32
    char buffer[sizeof(std::string)]{};
33
 
34
   std::string* c = new (buffer) std::string();
35
    
36
    std::string& rc = *c;
37
  
38
    rc = "hallo";
39
    rc = "bert";
40
    rc = std::string("uwe");
41
    rc.assign(std::string("uwe"));
42
  
43
    c->~basic_string();
44
  }
45
}

irgendjemand einen Idee - nach was ich schauen könnte? Warum z.B. die 
const char Zuweisung "geht"

von Vincent H. (vinci)


Lesenswert?

Deine Tests schreiben wohl alle in den internen Buffer von std::string 
(Stichwort SSO). Ich bin schon ziemlich sicher dass placement new über 
einen std::string ein Leak erzeugt sofern der String am Heap liegt.

Es wird ja
1.) Kein Dtor aufgerufen
2.) Wird der Ctor vom neuen std::string Objekt wohl kaum prüfen ob er 
"nicht eh schon was alloziert" hat...

von cppbert (Gast)


Lesenswert?

Vincent H. schrieb:

> (Stichwort SSO). Ich bin schon ziemlich sicher dass placement new über
> einen std::string ein Leak erzeugt sofern der String am Heap liegt.

und bist zu wenig eindeutig das man versteht welchen Teil du vom String 
meinst - die std::string Instanz selbst oder den zusätzlich verwalteten 
Puffer wenn der String zu gross wird (falls es Small-String Optimization 
gibt)

bei der string instanz selbst sollten während der Zuweisungen keine 
ctor/dtor Aktionen statt finden (wo für?)

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

cppbert schrieb:

> mein Kollege meinte das placement new und std::string nicht kompatible
> sind
> was für mich ein Ammenmärchen aus den Zeiten von Drachen und Rittern ist

Würde ich auch so sehen.

> irgendjemand einen Idee - nach was ich schauen könnte? Warum z.B. die
> const char Zuweisung "geht"

std::string kann höhere Anforderungen an das alignment haben. Während 
ein char[] wahrscheinlich an jeder Byte-Grenze ausgerichtet sein kann, 
wird das für std::string nicht gelten.

von cppbert (Gast)


Lesenswert?

Torsten R. schrieb:
> cppbert schrieb:
>
>> mein Kollege meinte das placement new und std::string nicht kompatible
>> sind
>> was für mich ein Ammenmärchen aus den Zeiten von Drachen und Rittern ist
>
> Würde ich auch so sehen.

Danke

>> irgendjemand einen Idee - nach was ich schauen könnte? Warum z.B. die
>> const char Zuweisung "geht"
>
> std::string kann höhere Anforderungen an das alignment haben. Während
> ein char[] wahrscheinlich an jeder Byte-Grenze ausgerichtet sein kann,
> wird das für std::string nicht gelten.

der String selbst ist mit alignof so wie auch der Kompiler/new es auf 
dem Stack/Heap machen würden alligned

von Roger S. (edge)


Lesenswert?

cppbert schrieb:
> mein Kollege meinte das placement new und std::string nicht kompatible
> sind

wen dem so waere, dann wuerde std::vector<std::string> nicht 
funktionieren.

> mein Test-Code der erstmal zeigt das placement new auf std::string nicht
> per se ein Problem ist

der zeigt auch nicht den code mit dem du ein problem hast.

Cheers, Roger

von Vincent H. (vinci)


Lesenswert?

cppbert schrieb:
> bei der string instanz selbst sollten während der Zuweisungen keine
> ctor/dtor Aktionen statt finden (wo für?)

Ah Pardon. Ich hab grad erst gesehn dass der String in einen char Buffer 
gelegt wird. Meine erste Deutung war irgendwie std::string über 
std::string (was mit placement new ebenfalls ginge).

Bitte mein erstes Posting ignorieren.

von mh (Gast)


Lesenswert?

cppbert schrieb:
> und bist zu wenig eindeutig

Dein Post, mit dem du den Thread gestartet hast, ist auch kein 
Musterbeispiel für Eindeutigkeit. Ich kann daraus nicht wirklich 
ableiten, was bei dir funktioniert und was nicht.

Wenn ich dich richtig verstehe
cppbert schrieb:
> mein Test-Code der erstmal zeigt das placement new auf std::string nicht
> per se ein Problem ist
funktioniert das Beispiel. Was funktioniert nicht?

von cppbert (Gast)


Lesenswert?

Roger S. schrieb:
> cppbert schrieb:
>> mein Kollege meinte das placement new und std::string nicht kompatible
>> sind
>
> wen dem so waere, dann wuerde std::vector<std::string> nicht
> funktionieren.

oder auch einfach new std::string() - der Unterschied zu placement new 
ist nur das er implicit mit sizeof(Type) allokiert - thats it

>> mein Test-Code der erstmal zeigt das placement new auf std::string nicht
>> per se ein Problem ist
>
> der zeigt auch nicht den code mit dem du ein problem hast.

an der Stelle passiert nur eine Zuweisung - der Initialisierungs-Code 
ist zu umfangreich um den hier einzustellen - deswegen meine Hoffnung 
das einer von euch eine (wilde) Idee hat

es passiert scheinbar nur wenn std::string(chars, chars + char_count)
mit im Spiel ist - und die Daten kommen aus einer deserialisierung

#if 0 // alles ohne leak

    // mit/ohne const kein leak
    std::string blub = "blub";
    ziel = blub;
    ziel.assign( blub );
    ziel.assign( std::string(blub) );
    ziel.assign( blub.begin(), blub.end() );
    ziel.assign( std::string(blub.begin(), blub.end() ) );

    ziel = "blub";
    ziel.assign( "blub" );
    ziel.assign( std::string("blub") );
#else // leaken beide

    const std::string x = std::string(chars, chars + char_count);
    ziel = x;
    //ziel.assign(x);
#endif

und die ganzen Zuweisungen landen wegen dem const eh alle in einem 
Kopieraktion
deswegen ist mir so unklar wo da der Fehler herkommen soll

oder mein chars-Pointer+chars+chars_count ist irgendwie korrupt
muss da der char-Ptr aligned sein?

der Unit-Test bringt auch ordentlich Last mit ein paar tausend 
Allokationen/Zuweisungen
dachte das man damit irgendwelche Korruptionen doch auch erkennen 
muesste (über die Zeit) - aber nur ASAN meldet was - das System laeuft 
scheinbar stabil

von Roger S. (edge)


Lesenswert?

Immer noch nicht der ganze code. Rufst du auch den destructor des ziel 
strings auf, so wies du bei dem 'funktionierenden' Beispiel tust?

von mh (Gast)


Lesenswert?


von cppbert (Gast)


Lesenswert?

Roger S. schrieb:
> Immer noch nicht der ganze code. Rufst du auch den destructor des
> ziel
> strings auf, so wies du bei dem 'funktionierenden' Beispiel tust?

ja das mache ich - und der ganze Code sind ein paar hundert Zeilen - 
wenn ich den so reduziere das ich es hier zeigen kann kommt sowas raus 
wie in meinem Beispiel

von cppbert (Gast)


Lesenswert?

mh schrieb:
> Was wir dir sagen wollen:
> https://stackoverflow.com/help/minimal-reproducible-example

wenn ich das Szenario so weit eindampfe - siehe Beispielcode vom 
Eingangspost passiert der Fehler nicht mehr - sind ~500 Zeilen Code 
direkt beteiligt

von Roger S. (edge)


Lesenswert?

cppbert schrieb:
> ja das mache ich - und der ganze Code sind ein paar hundert Zeilen -
> wenn ich den so reduziere das ich es hier zeigen kann kommt sowas raus
> wie in meinem Beispiel

Dann mach es, moeglicherweise wirst du dann feststellen dass das Problem 
nicht dort liegt wo du es vermutest. Ich geh davon aus dass char_count 
groesser als das SSO limit Deiner string implementation ist, und es 
daher einen Unterschied macht ob der Destructor des strings aufgerufen 
wird. Das ist in etwa das was ich anhand der gezeigten Schnippsel 
wahrsagen kann.

von Klaus W. (mfgkw)


Lesenswert?

Vincent H. schrieb:
> Es wird ja
> 1.) Kein Dtor aufgerufen

Vincent H. schrieb:
> Bitte mein erstes Posting ignorieren.

Vielleicht doch nicht ignorieren.

Bei einem placement new wird beim folgenden delete wohl kein Destruktor 
aufgerufen:
https://stackoverflow.com/questions/6783993/placement-new-and-delete

Das müsste man manuell nachholen.

von mh (Gast)


Lesenswert?

cppbert schrieb:
> mh schrieb:
>> Was wir dir sagen wollen:
>> https://stackoverflow.com/help/minimal-reproducible-example
>
> wenn ich das Szenario so weit eindampfe - siehe Beispielcode vom
> Eingangspost passiert der Fehler nicht mehr - sind ~500 Zeilen Code
> direkt beteiligt

Das ist unwahrscheinlich. Und dein Beispiel im Eingangspost hat nichts 
mit dem mimimalen Beispiel zu tun, das in dem Link beschrieben wird. 
Dein Beispiel besteht aus 4 seperaten Teilen, von denen keins den Fehler 
reproduziert.

von cppbert (Gast)


Lesenswert?

mh schrieb:
> cppbert schrieb:
>> mh schrieb:
>>> Was wir dir sagen wollen:
>>> https://stackoverflow.com/help/minimal-reproducible-example
>>
>> wenn ich das Szenario so weit eindampfe - siehe Beispielcode vom
>> Eingangspost passiert der Fehler nicht mehr - sind ~500 Zeilen Code
>> direkt beteiligt
>
> Das ist unwahrscheinlich. Und dein Beispiel im Eingangspost hat nichts
> mit dem mimimalen Beispiel zu tun, das in dem Link beschrieben wird.
> Dein Beispiel besteht aus 4 seperaten Teilen, von denen keins den Fehler
> reproduziert.

das Beispiel war nur um zu zeigen das placement new in den Situationen 
völlig normal funktioniert - den Fehler kann man einfach mit weglassen 
vom dtor provozieren - ABER ich hab es jetzt doch schon gefunden

das ganze placement-newing ist unter einer komischen alloc/free API 
verborgen zu der es auch eine automatisch aufräumende Variante gibt

der Unit-Test nutzt die manuelle Variante (was falsch ist, wovon ich 
aber ausgegangen bin) und es fehlte einfach mitten drin in den Tests ein 
free-Aufruf an diese API :/

ASAN/LSAN hat für den Leak-Ursprung auch nur so eine unsymbolize Ausgabe 
gemacht - das hat mich irritiert weil ich sonst immer schön die 
Quelltexttzeilen usw. bekommen

Kurzum: komplett an der falschen Stelle gesucht weil ich von einer 
anderen Grundlage ausgegangen bin

Danke für eure Teilnahme an dem Spass :)

von mh (Gast)


Lesenswert?

cppbert schrieb:
> Kurzum: komplett an der falschen Stelle gesucht weil ich von einer
> anderen Grundlage ausgegangen bin

Kurz gesagt: Hättest du das minimale Beispiel gebaut, hättest du den 
Fehler gefunden. Ein "minimales compilierbares Fehler reproduzierendes 
Beispiel" ist nicht nur der beste Weg nach Hilfe zu Fragen, es ist auch 
ein sehr guter Weg den Fehler selbst zu finden.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

mh schrieb:
> Kurz gesagt: Hättest du das minimale Beispiel gebaut, hättest du den
> Fehler gefunden. Ein "minimales compilierbares Fehler reproduzierendes
> Beispiel" ist nicht nur der beste Weg nach Hilfe zu Fragen, es ist auch
> ein sehr guter Weg den Fehler selbst zu finden.

Der OP hatte uns darum gebeten, eine seiner Annahmen zu verifizieren und 
dabei sein Problem erläutert.

Das Reduzieren eines Problems auf ein Minimum kann schon mal mehrere 
Tage dauern. Was spricht dagegen, grundsätzliche Annahmen vorher schon 
mal zu verifizieren?

von cppbert (Gast)


Lesenswert?

mh schrieb:
> cppbert schrieb:
>> Kurzum: komplett an der falschen Stelle gesucht weil ich von einer
>> anderen Grundlage ausgegangen bin
>
> Kurz gesagt: Hättest du das minimale Beispiel gebaut, hättest du den
> Fehler gefunden. Ein "minimales compilierbares Fehler reproduzierendes
> Beispiel" ist nicht nur der beste Weg nach Hilfe zu Fragen, es ist auch
> ein sehr guter Weg den Fehler selbst zu finden.

Ich weiß doch das ein fehlender dtor-Aufruf mit einem placement new 
std::string ein Leak erzeugen (kann) das wäre keine neue Erkenntnis 
gewesen

aber du hast definitiv recht weil die ASAN/LSAN Meldung wäre dann so 
schön identisch gewesen und ich wäre wohl schneller auf die Idee 
gekommen an der richtigen Stellen zu schauen

von mh (Gast)


Lesenswert?

cppbert schrieb:
> mh schrieb:
>> cppbert schrieb:
>>> Kurzum: komplett an der falschen Stelle gesucht weil ich von einer
>>> anderen Grundlage ausgegangen bin
>>
>> Kurz gesagt: Hättest du das minimale Beispiel gebaut, hättest du den
>> Fehler gefunden. Ein "minimales compilierbares Fehler reproduzierendes
>> Beispiel" ist nicht nur der beste Weg nach Hilfe zu Fragen, es ist auch
>> ein sehr guter Weg den Fehler selbst zu finden.
>
> Ich weiß doch das ein fehlender dtor-Aufruf mit einem placement new
> std::string ein Leak erzeugen (kann) das wäre keine neue Erkenntnis
> gewesen

Und wie sollen wir feststellen, dass ein dtor fehlt? Aus deinem Post 
wird nichtmal deutlich, ob im realen Programm, das den Fehler enthält, 
Raw-Pointer vorkommen.

Torsten R. schrieb:
> Das Reduzieren eines Problems auf ein Minimum kann schon mal mehrere
> Tage dauern. Was spricht dagegen, grundsätzliche Annahmen vorher schon
> mal zu verifizieren?

Wenn es mehrere Tage dauert das Problem zu minimieren ist das halt so. 
Die Arbeit kann man ihm nicht abnehmen. Wenn er eine konkrete Frage hat 
ist das was anderes.

von cppbert (Gast)


Lesenswert?

mh schrieb:
>> Ich weiß doch das ein fehlender dtor-Aufruf mit einem placement new
>> std::string ein Leak erzeugen (kann) das wäre keine neue Erkenntnis
>> gewesen
>
> Und wie sollen wir feststellen, dass ein dtor fehlt? Aus deinem Post
> wird nichtmal deutlich, ob im realen Programm, das den Fehler enthält,
> Raw-Pointer vorkommen.

was hat das jetzt mit Raw-Pointern zu tun?
der sich im std::string befindliche Point auf den Heap-String wurde 
nicht aufgeräumt weil der dtor des std::strings nicht explizit 
aufgerufen wurde
das ist aber alles unter einer riesigen API verborgen und schlussendlich 
ist ein Test eben nicht Test-Konform und keinem ists aufgefallen

mh schrieb:
> Torsten R. schrieb:
>> Das Reduzieren eines Problems auf ein Minimum kann schon mal mehrere
>> Tage dauern. Was spricht dagegen, grundsätzliche Annahmen vorher schon
>> mal zu verifizieren?
>
> Wenn es mehrere Tage dauert das Problem zu minimieren ist das halt so.
> Die Arbeit kann man ihm nicht abnehmen. Wenn er eine konkrete Frage hat
> ist das was anderes.

dafür war das Problem dann aber doch nicht groß genug - Denkanstoß 
reicht oft auch schon aus


aber habt ihr vielleicht noch einen Tip wie ich die ASAN/LSAN Meldung 
noch verbessern könnten

main.cpp
1
#include <string>
2
3
int main()
4
{
5
  {
6
    std::string* s = new std::string();
7
    s->operator=("wir brechen mal ordentlich aus der der small string optimization aus!");
8
    //delete s;
9
  }
10
  
11
  {
12
    char buffer[sizeof(std::string)]{};
13
    std::string* s = new (buffer) std::string();
14
    s->operator=("wir brechen mal ordentlich aus der der small string optimization aus!");
15
    //s->~basic_string();
16
  }
17
}

gebaut mit Clang 13.x unter Ubuntu 21.10
1
clang++ -g -fno-omit-frame-pointer -fsanitize=address main.cpp

wenn ich das "delete s" auskommentiere kommt von ASAN/LSAN diese Meldung
1
==2462==ERROR: LeakSanitizer: detected memory leaks
2
3
Direct leak of 32 byte(s) in 1 object(s) allocated from:
4
    #0 0x4ca82d in operator new(unsigned long) (/home/linux/dev/test/pn/a.out+0x4ca82d)
5
    #1 0x4cd1e3 in main /home/linux/dev/test/pn/main.cpp:6:22
6
    #2 0x7fbd0f242fcf in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
7
8
Indirect leak of 70 byte(s) in 1 object(s) allocated from:
9
    #0 0x4ca82d in operator new(unsigned long) (/home/linux/dev/test/pn/a.out+0x4ca82d)
10
    #1 0x7fbd0f68228d in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x14728d)
11
12
SUMMARY: AddressSanitizer: 102 byte(s) leaked in 2 allocation(s).

sehr schön zu erkennen ist das man deutliche die Quelltextzeile des 
Leak-Urpsrungs bekommt
1
    #1 0x4cd1e3 in main /home/linux/dev/test/pn/main.cpp:6:22

wenn ich den "s->~basic_string()" auskommentiere kommt z.B. kein 
direkter Quelltext-Bezug mehr raus
1
==2473==ERROR: LeakSanitizer: detected memory leaks
2
3
Direct leak of 70 byte(s) in 1 object(s) allocated from:
4
    #0 0x4ca82d in operator new(unsigned long) (/home/linux/dev/test/pn/a.out+0x4ca82d)
5
    #1 0x7f993727b28d in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x14728d)
6
7
SUMMARY: AddressSanitizer: 70 byte(s) leaked in 1 allocation(s).

kann man das mit Parametern für GCC oder ASAN informativer machen
oder ist das das maximum was man fuer placement new so direkt bekommt

von cppbert (Gast)


Lesenswert?

Mir ist klar das die beiden Fälle nicht ganz gleich sind - aber könnte 
der ASAN/LSAN nicht sagen das im 2. Fall die Zeile 13 daran beteiligt 
ist?

von mh (Gast)


Lesenswert?

cppbert schrieb:
> was hat das jetzt mit Raw-Pointern zu tun?
> der sich im std::string befindliche Point auf den Heap-String wurde
> nicht aufgeräumt weil der dtor des std::strings nicht explizit
> aufgerufen wurde

Und warum wurde der dtor nicht aufgerufen? Weil ein Raw-Pointer benutzt 
wurde, um den string zu speichern.

von cppbert (Gast)


Lesenswert?

mh schrieb:
> cppbert schrieb:
>> was hat das jetzt mit Raw-Pointern zu tun?
>> der sich im std::string befindliche Point auf den Heap-String wurde
>> nicht aufgeräumt weil der dtor des std::strings nicht explizit
>> aufgerufen wurde
>
> Und warum wurde der dtor nicht aufgerufen? Weil ein Raw-Pointer benutzt
> wurde, um den string zu speichern.

placement new und Smart-Pointer macht recht wenig Sinn

der std::string wirde mit placement new in einen grossen Puffer 
eingebettet
das Leak kommt von dem internen String auf dem Heap den std::string 
verwaltet wenn der die String die Small-String Optimization sprengt und 
am Ende der dtor nicht aufgerufen wird - das geht nicht mit einem 
Smart-Pointer - oder macht eben absolut keinen Sinn

Es ist eine dynamisches Type-System das zur Laufzeit aus 
Type-Hierarchien schnelle/effiziente Datenspeicher erzeugen kann - und 
diese API wurde im Test nicht richtig verwendet - davon bin ich aber 
ausgegangen weil es sonst einfach zu unsicher ist

von mh (Gast)


Lesenswert?

cppbert schrieb:
> placement new und Smart-Pointer macht recht wenig Sinn
>
> der std::string wirde mit placement new in einen grossen Puffer
> eingebettet
> das Leak kommt von dem internen String auf dem Heap den std::string
> verwaltet wenn der die String die Small-String Optimization sprengt und
> am Ende der dtor nicht aufgerufen wird - das geht nicht mit einem
> Smart-Pointer - oder macht eben absolut keinen Sinn

Da solltest du nochmal drüber nachdenken. Was genau spricht gegen einen 
Smart-Pointer?

von cppbert (Gast)


Lesenswert?

mh schrieb:
> cppbert schrieb:
>> placement new und Smart-Pointer macht recht wenig Sinn
>>
>> der std::string wirde mit placement new in einen grossen Puffer
>> eingebettet
>> das Leak kommt von dem internen String auf dem Heap den std::string
>> verwaltet wenn der die String die Small-String Optimization sprengt und
>> am Ende der dtor nicht aufgerufen wird - das geht nicht mit einem
>> Smart-Pointer - oder macht eben absolut keinen Sinn
>
> Da solltest du nochmal drüber nachdenken. Was genau spricht gegen einen
> Smart-Pointer?

ich bin mir nicht sicher ob du technisch verstehst was die Leak-Ursache 
ist

der grosse Puffer ist schlussendlich einfach ein std::vector da braucht 
es keinen Smart-Pointer

und wenn du 100 Variablen 
std::strings/ints/struct/arrays/vektoren/doubles die teilweise selbst 
hierarchisiert sind kompakt in den Puffer (mit entsprechendem alignment) 
anordnen willst um eben keine 100 Einzelallokationen zu haben dann 
kannst du hier nirgends einen Smart-Pointer einbauen - der macht den 
ganzen placement new Vorteil vollständig zunichte - es geht um hohe 
Flexibilität zur Laufzeit und Gigabytes an Daten im Simulationsumfeld

von mh (Gast)


Lesenswert?

cppbert schrieb:
> ich bin mir nicht sicher ob du technisch verstehst was die Leak-Ursache
> ist
>
> der grosse Puffer ist schlussendlich einfach ein std::vector da braucht
> es keinen Smart-Pointer

Der große Puffer ist ja auch nicht dein Problem, er leakt nicht. Es geht 
um den String in deinem Beispiel. Du benutzt einen Raw-Pointer, der auf 
diesen String zeigt. Der dtor dieses Strings wird nicht aufgerufen. 
Verstehst du das Problem? Könnte ein Smart-Pointer, der automatisch den 
dtor aufruft das Problem lösen?

von cppbert (Gast)


Lesenswert?

mh schrieb:
> cppbert schrieb:
>> ich bin mir nicht sicher ob du technisch verstehst was die Leak-Ursache
>> ist
>>
>> der grosse Puffer ist schlussendlich einfach ein std::vector da braucht
>> es keinen Smart-Pointer
>
> Der große Puffer ist ja auch nicht dein Problem, er leakt nicht. Es geht
> um den String in deinem Beispiel. Du benutzt einen Raw-Pointer, der auf
> diesen String zeigt.

ja, damit an den Speicherbereich des std::string komme - der irgendwo in 
dem placement new buffer ist

> Der dtor dieses Strings wird nicht aufgerufen.
> Verstehst du das Problem? Könnte ein Smart-Pointer, der automatisch den
> dtor aufruft das Problem lösen?

eher nicht - weil dazu muss der Smart-Pointer doch die Kontrolle über 
den std::string haben - was er aber nicht haben kann weil dieser 
eingebettet in dem placement new buffer ist (mit vielen anderen 
Variablen zusammen)

wenn man placement new verwendet verliert man jeglichen Automatismus 
bezüglich aufräumen - ausser eben der komplette placement Puffer selbst

hier ein Beispiel was das System macht

durch die dynamische Typ-Beschreibung ist klar das mein Typ
aus einem Double, Int8, String und besteht (die intern abbildung davon 
ist ein echter double, int8_t und std::string) - Struct, Arrays/Vektoren 
verwenden
eigenen Abbildungen die zur Laufzeit aber dem Verhalten von struct, 
std::array und std::vector entsprechen (aber eben nicht auf diesen 
basieren)

dann wird in etwa das gemacht

(alles zur Laufzeit)

1. placement_new_buffer_size = summe über alle Typen.sizeof() + 
alignment beachten
2. std::vector<uint8_t> buffer(palcement_new_buffer_size);

3. die Laufzeit-Typen die initalisierung benötigen z.B. std::string oder 
std::vector werden durch das Type-System in dem buffer initialisiert

jetzt ist im buffer der Aufbau
---
0: sizeof(double) bytes 8
8: sizeof(int8_t) bytes 1
10: sizeof(std::string(der hat selbst einen Zeiger)) bytes (~30-40)
---

der buffer sieht jetzt exakt so aus wie ein C++ struct der Art im 
Speicher
- nur das es eben alles dynamisch erzeugt wurde

da gibt es jetzt keinen Pointer die zu verwalten sind
und irgendwas wie std::vector<std::any<std::unique_ptr<...>>>>
würde die Performanz und Kompatkanforderung völlig zerstören

wo siehst du da eine Maglichkeit einen Smart-Pointer ein zu bauen

1. den std::string kann und will ich nicht aendern
2. die Elemente Einzel auf den Heap zu stellen ist sinnfrei
3. ein Smart-Pointer !darf hier nicht deleten! - der dürfte maximal den 
dtor
Aufrufen - weil der buffer ja jemand anderem gehört

der dtor wird vom dynamischen Type-System für alle Kinder in dem Puffer 
gezielt aufgerufen - weil die ein Out-of-Scope gehen gar nicht 
mitbekommen
- und genau das hat in dem Unit-Test gefehlt - das darüberliegende 
System macht das automatisch - aber das hat der Unit-Test Entwickler 
leider nicht verwendet

RAII und placement new kombinieren sich nicht gut - oder du kannst
dir placement new sparen

von cppbert (Gast)


Lesenswert?

alle Objekte die du in placement new Puffer steckst können nicht mehr 
ohne manuellen Aufwand detored werden (deren internen Objekt-Members 
werden aber durch den äußeren dtor Aufruf wie üblich weiter verarbeitet)

von mh (Gast)


Lesenswert?

cppbert schrieb:
> [...]
> eher nicht - weil dazu muss der Smart-Pointer doch die Kontrolle über
> den std::string haben
> [...]

Je, er muss die Kontrolle haben, den Destruktor aufzurufen. Als Hinweis:
1
template<
2
    class T,
3
    class Deleter = std::default_delete<T>
4
> class unique_ptr;

von cppbert3 (Gast)


Lesenswert?

mh schrieb:
> cppbert schrieb:
>
>> [...]
>> eher nicht - weil dazu muss der Smart-Pointer doch die Kontrolle über
>> den std::string haben
>> [...]
>
> Je, er muss die Kontrolle haben, den Destruktor aufzurufen. Als Hinweis:
> template<
>     class T,
>     class Deleter = std::default_delete<T>
>> class unique_ptr;

Selbst wenn man den Deleter ersetz wird der dtor vom unique_ptr trotzdem 
nicht automatisch aufgerufen weil der unique_ptr nicht out of scope 
geht, sollte dieser im placement new buffer liegen

und um von aussen mit einem nur-dtorendem pseudo Smartpointer in den 
puffer hinnein zu zeigen braucht man ja wieder einen vector oder 
aehnliches um diesen oder mehreren smart pointer einen scope zu geben 
und der muss auch noch typesafe sein d.h. ein simple vector mit 
smartpointern reicht nicht, und das ist wieder alles andere als kompakt

von mh (Gast)


Lesenswert?

cppbert3 schrieb:
> elbst wenn man den Deleter ersetz wird der dtor vom unique_ptr trotzdem
> nicht automatisch aufgerufen weil der unique_ptr nicht out of scope
> geht, sollte dieser im placement new buffer liegen
Du sollst nicht den unique_ptr in den Buffer stecken.

cppbert3 schrieb:
> und um von aussen mit einem nur-dtorendem pseudo Smartpointer in den
> puffer hinnein zu zeigen braucht man ja wieder einen vector oder
> aehnliches um diesen oder mehreren smart pointer einen scope zu geben
> und der muss auch noch typesafe sein d.h. ein simple vector mit
> smartpointern reicht nicht, und das ist wieder alles andere als kompakt
Was soll jetzt dein "pseudo Smartpointer" sein?

Und wenn es jemanden gibt, der den richtigen Destruktor aufrufen kann, 
gibt es auch einen möglichen Owner für einen Smart-Pointer. Irgendwer 
muss sich ja merken, was in dem Speicher steht.

von cppbert3 (Gast)


Lesenswert?

mh schrieb:
> Was soll jetzt dein "pseudo Smartpointer" sein?

Ein smartpointer der nicht deleted sondern nur den dtor Aufruft

> Und wenn es jemanden gibt, der den richtigen Destruktor aufrufen kann,
> gibt es auch einen möglichen Owner für einen Smart-Pointer. Irgendwer
> muss sich ja merken, was in dem Speicher steht.

Wie gesagt das gibt es auf einer höheren Ebene, ist nur kein 
"Smartpointer" aber trotzdem RAII, d.h. also auch Exception Safe usw. 
Aber der Unit Tester hat die non public API getestet, was er gar nicht 
durfte

weiter unten könnte man das auch sicherer machen, aber ich würde das 
"aufräumen" in dem zusammenhang trotzdem nicht als Smartpointer 
bezeichnen - finde ich ein wenig irreführend

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.