std::shared_ptr und dynamische Arrays



  • Manchmal moechte man std::vector nicht benutzen, weil bei der Konstruktion die Elemente initialisiert werden. std::vector ist eben kein roher Speicher.


  • Mod

    DocShoe schrieb:

    Wenn du den Kopieroverhead von vector vermeiden willst kannst du ihn in einen shared_ptr verpacken.

    Oder keine unnötigen Kopien durchführen? Lieber lernen, richtig mit der Sprache umgehen, als eine zusätzliche, unnötige Indirektion einzuführen, um vermeidbare Fehler auszuschließen. C++ ist kein Ponyhof, daher reiten die Programme auch so schnell, solange man ihnen nicht künstlich Gewichte anhängt*.

    *: Mein Beitrag zur viel zu weit getriebene Metapher des Jahres 🙂 .


  • Mod

    knivil schrieb:

    Manchmal moechte man std::vector nicht benutzen, weil bei der Konstruktion die Elemente initialisiert werden. std::vector ist eben kein roher Speicher.

    new auch nicht.

    Für rohen Speicher gibt es die Funktionen aus dem memory-Header.



  • Okay, dann formuliere ich es als Frage: Wird bei std::vector<int>(k) jedes Element auf 0 gesetzt? Passiert das auch bei new int[k]? Was passiert bei char? Und was muss ich tun, um rohen Speicher zu erhalten (im Gegensatz zu new char[...])?

    Wenn man nach Usecases sucht, dann findet man nur http://stackoverflow.com/questions/3264299/why-do-i-need-stdget-temporary-buffer



  • insistent schrieb:

    Ki schrieb:

    Eigentlich wollte ich ja gerade NICHT auf std::vector zurückgreifen. 😃

    wieso? hast du einen grund dafür? zu langsam? zu kompliziert? bugs? großvater wurde von vector erschlagen?

    Ach, nur eine Geschmacksfrage. Ich implementiere gerade eine Klasse für mathematische Vektoren dynamischer Größe und fand es für mich angenehmer, die Daten selbst in Form einer Array-List zu implementieren. Was im Endeffekt auf dasselbe hinauskommt, aber ich finde es, wie gesagt, angenehmer.

    camper schrieb:

    Ki schrieb:

    nutzt die Klasse dann automatisch delete[]

    nein.

    Ki schrieb:

    brauche ich einen benutzerdefinierten Deleter?

    ja, wobei der Standard schon einen passenden in Form von

    std::default_deleter<T[]>
    

    hat.

    unique_ptr hat eine Spezialsierung für arrays:

    std::unique_ptr<T[]>
    

    die neben dem passenden Deleter auch auch über einen entsprechend überladenen []-Operator verfügt.

    Danke 🙂 std::default_deleter<T[]> hatte ich ganz vergessen



  • Doch noch einmal ein paar weiterführende Fragen. Ich hoffe, es ist ok, den Thread einfach weiter zu nutzen, anstatt einen neuen zu öffnen.
    Ich denke, es ist doch besser, std::vector direkt zu nutzen, bevor ich es komplett selbst reimplementiere, zumal ich ohnehin schon viel auf die STD-Library zugreife.

    Nun wäre es eine Möglichkeit, das gesagte std::vector -Interface mittels inline Funktionen nachzutippen und alles durchzureichen, was im Prinzip nur langweilige Tipparbeit ist. Nun ist es ja aber so, dass Inline nicht immer vom Compiler inlined wird. Die Frage wäre, ob der Overhead durch Funktionsaufrufe in dem Fall signifikant wäre oder ob ich mich einfach auf den Compiler verlassen kann.

    Eine weitere Möglichkeit wäre Vererbung und das Hinzufügen arithmetischer Funktionen.
    Ist std::vector denn sicher zum öffentlichen Erben?

    MfG, Ki



  • Ki schrieb:

    Ist std::vector denn sicher zum öffentlichen Erben?

    Nur wenn du keine Polymorphie brauchst. Die Destruktoren sind nicht virtual.



  • out schrieb:

    Ki schrieb:

    Ist std::vector denn sicher zum öffentlichen Erben?

    Nur wenn du keine Polymorphie brauchst. Die Destruktoren sind nicht virtual.

    Hatte ich befürchtet 😕
    Aber das heißt, wenn ich keine virtuellen Methoden habe, macht ein nicht-virtueller Destruktor in der Basisklasse gar nichts? Wusste ich auch noch nicht.


  • Mod

    Ki schrieb:

    Aber das heißt, wenn ich keine virtuellen Methoden habe, macht ein nicht-virtueller Destruktor in der Basisklasse gar nichts? Wusste ich auch noch nicht.

    😕 Ich glaube, du hast da was falsch verstanden.

    class foo{};
    class bar:public foo{};
    
    foo* f = new bar;
    delete f;  // Ohje! Der bar-Teil wurde nicht zerstört.
    
    class foo{virtual int i(){}};
    class bar:public foo{};
    
    foo* f = new bar;
    delete f;  // Ohje! Der bar-Teil wurde nicht zerstört.
    
    class foo{virtual ~foo(){}};
    class bar:public foo{virtual ~bar(){}};
    
    foo* f = new bar;
    delete f;  // Passt
    
    class foo{virtual ~foo(){} virtual int i(){}};
    class bar:public foo{virtual ~bar(){}};
    
    foo* f = new bar;
    delete f;  // Passt
    

    Jetzt sind die Beispiele 1 und 3 aber eher hypothetisch, da besitzende Basisklassenzeiger allerhöchstens dann Sinn machen, wenn man damit Polymorphie machen möchte. Beispiel 2 ist das, vor dem du gewarnt wurdest. Beispiel 4 ist, wie es richtig geht. Ob die erste virtuelle Methode in foo, in bar oder einer späteren Klasse auftaucht, ist für die Aussage des Beispiels egal.



  • Ok, ich habe das bisher nicht weiter hinterfragt und generell Destruktoren virtuell (und noexcept) gemacht. 😃 Und normalerweise erbe ich auch nie von STL-Typen, daher die Frage.

    Also wenn keine virtuellen Funktionen in der Klassenhierarchie auftauchen, ist es egal, ob die Destruktoren virtuell sind?


  • Mod

    Ki schrieb:

    Also wenn keine virtuellen Funktionen in der Klassenhierarchie auftauchen, ist es egal, ob die Destruktoren virtuell sind?

    Du brauchst einen virtuellen Destruktor, wenn du ein Objekt über einen Basisklassenzeiger zerstören möchtest. Was allerhöchstens dann Sinn macht, wenn man virtuelle Funktionen in der Hierarchie hat. Denn wieso sollte man sonst jemals einen besitzenden Basisklassenzeiger haben?



  • SeppJ schrieb:

    Ki schrieb:

    Also wenn keine virtuellen Funktionen in der Klassenhierarchie auftauchen, ist es egal, ob die Destruktoren virtuell sind?

    Du brauchst einen virtuellen Destruktor, wenn du ein Objekt über einen Basisklassenzeiger zerstören möchtest. Was allerhöchstens dann Sinn macht, wenn man virtuelle Funktionen in der Hierarchie hat. Denn wieso sollte man sonst jemals einen besitzenden Basisklassenzeiger haben?

    Ahhhh, jetzt sehe ich das Problem erst! 🙄
    Gut, dass ich hier keine Basisklassenzeiger verwenden will, ist klar und man kann es dokumientieren. Aber gibt es auch Programmiertechnische Maßnahmen, um zu verhindern, dass Basisklassenzeiger überhaupt angelegt werden können?


  • Mod

    Ki schrieb:

    Aber gibt es auch Programmiertechnische Maßnahmen, um zu verhindern, dass Basisklassenzeiger überhaupt angelegt werden können?

    private Vererbung



  • camper schrieb:

    Ki schrieb:

    Aber gibt es auch Programmiertechnische Maßnahmen, um zu verhindern, dass Basisklassenzeiger überhaupt angelegt werden können?

    private Vererbung

    Dann könnte ich auch gleich wieder eine Aggregation nehmen und alle Methoden durchreichen.


  • Mod

    Ki schrieb:

    camper schrieb:

    Ki schrieb:

    Aber gibt es auch Programmiertechnische Maßnahmen, um zu verhindern, dass Basisklassenzeiger überhaupt angelegt werden können?

    private Vererbung

    Dann könnte ich auch gleich wieder eine Aggregation nehmen und alle Methoden durchreichen.

    oder einfach using-Deklarationen verwenden



  • camper schrieb:

    Ki schrieb:

    camper schrieb:

    Ki schrieb:

    Aber gibt es auch Programmiertechnische Maßnahmen, um zu verhindern, dass Basisklassenzeiger überhaupt angelegt werden können?

    private Vererbung

    Dann könnte ich auch gleich wieder eine Aggregation nehmen und alle Methoden durchreichen.

    oder einfach using-Deklarationen verwenden

    Oh 😃 Gut, DAS ist ein toller Einwand, danke 🙂



  • knivil schrieb:

    Okay, dann formuliere ich es als Frage: Wird bei std::vector<int>(k) jedes Element auf 0 gesetzt?
    Passiert das auch bei new int[k]? Was passiert bei char? Und was muss ich tun, um rohen Speicher zu erhalten (im Gegensatz zu new char[...])?

    In allen Fällen wird der Skalar nicht initialisiert, denn alle Elemente werden default-initialized , was bei Skalaren bedeutet dass nichts geschieht. Genauso bekommst du auch rohen Speicher. Denn wenn du ein Array von Skalaren hast, dann werden diese (solange du keinen Initializer im Deklarator hast) default-initialized.
    (Willst du hingegen, dass jedes Element value-initialized wird, dann schreibst du new char[...]() , siehe auch §8.5/11).

    Im Standard ist das ganze folgendermaßen zu deduzierenn.

    N3690 §8.5/11 schrieb:

    If no initializer is specified for an object, the object is default-initialized.

    Für Arrays heißt das:

    N3690 §8.5/7 schrieb:

    To default-initialize an object of type T means:
    — if T is an array type, each element is default-initialized;

    Und für Skalare heißt das:

    To default-initialize an object of type T means:
    — otherwise [(Die beiden anderen Stichpunkte sind für Klassen und Arrays)], no initialization is performed.

    Bei vector geht das eine ganze Weile so, bis man wieder bei new ankommt. Da bei default-insertion keine Parameter an std::allocator::construct mitgegeben werden, wird es value-initialized*.

    *Dank der Syntax die der Standard für den Aufruf von new festlegt.



  • Sone schrieb:

    Bei vector geht das eine ganze Weile so, bis man wieder bei new ankommt. Da bei default-insertion keine Parameter an std::allocator::construct mitgegeben werden, wird es default-initialized.

    Knapp dabei ist auch daneben.



  • vaterundsohn schrieb:

    Sone schrieb:

    Bei vector geht das eine ganze Weile so, bis man wieder bei new ankommt. Da bei default-insertion keine Parameter an std::allocator::construct mitgegeben werden, wird es default-initialized.

    Knapp dabei ist auch daneben.

    ::new ((void*)c) C(forward<Args>(args)...)
    

    Oh, du hast Recht. Die Klammer bleibt bestehen... mein Fehler. Dann ist es value-initialization, und es wird zero-initialized (da Skalar)...



  • Also wird bei std::vector zero-initialized und bei new default-initialized?


Anmelden zum Antworten