Warum wird das Erstellen von STL-Containern dynamisch als eine schlechte Vorgehensweise angesehen?

7

Der Titel sagt es.

Beispiel für schlechte Praxis:

%Vor%

Was ist daran falsch, wenn ich das vector später lösche?

Ich programmiere meistens in C #, daher ist dieses Problem für mich im C ++ Kontext nicht klar.

    
Andrey 20.02.2011, 23:53
quelle

6 Antworten

11

Als Faustregel gilt: Je weniger Sie auf dem Heap reservieren, desto weniger riskieren Sie, Speicher zu verlieren. :)

std::vector ist auch nützlich, weil es automatisch den Speicher verwaltet, der für den Vektor in RAII-Mode verwendet wird; Indem Sie es jetzt auf dem Heap zuweisen, benötigen Sie eine explizite Freigabe (mit delete result ), um zu vermeiden, dass der Speicher ausgelaufen wird. Die Sache wird wegen Ausnahmen kompliziert gemacht, die deinen Rückweg ändern und jede delete überspringen können, die du auf den Weg bringst. (In C # gibt es solche Probleme nicht, da nicht zugreifbarer Speicher nur periodisch vom Garbage Collector aufgerufen wird)

Wenn Sie einen STL-Container zurückgeben möchten, haben Sie mehrere Möglichkeiten:

  • gib es einfach als Wert zurück; Theoretisch sollten Sie wegen der Provisorien, die bei der Rückgabe von result erstellt werden, in einer Kopier-Penaltion auftreten, aber neuere Compiler sollten in der Lage sein, die Kopie mithilfe von NRVO 1 . Es kann auch std::vector Implementierungen geben, die eine Kopie-auf-Schreib-Optimierung implementieren, wie viele std::string Implementierungen, aber ich habe noch nie davon gehört.

    Bei C ++ 0x-Compilern sollte stattdessen die Verschiebungssemantik ausgelöst werden, die jede Kopie vermeidet.

  • Speichern Sie den Zeiger des Ergebnisses in einem eigentümerübertragenden intelligenten Zeiger wie std::auto_ptr (oder std::unique_ptr in C ++ 0x) und ändern Sie auch den Rückgabetyp Ihrer Funktion in std::auto_ptr<std::vector<Point > > ; Auf diese Weise wird Ihr Zeiger immer in einem Stapel-Objekt eingekapselt, das automatisch zerstört wird, wenn die Funktion (auf irgendeine Weise) beendet wird, und zerstört das vector , wenn es noch immer von ihm gehört. Außerdem ist völlig klar, wem das zurückgegebene Objekt gehört.
  • Machen Sie den result vector zu einem Parameter, der vom Aufrufer als Referenz übergeben wird, und füllen Sie diesen, anstatt einen neuen Vektor zurückzugeben.
  • Hardcore-STL-Option: Sie würden stattdessen Ihre Daten als Iteratoren bereitstellen; Der Client-Code würde dann std::copy + std::back_inserter oder was auch immer verwenden, um solche Daten in dem gewünschten Container zu speichern. Nicht viel gesehen (es kann schwierig sein, richtig zu codieren), aber es ist erwähnenswert.
  1. Wie @Steve Jessop in den Kommentaren darauf hingewiesen hat, funktioniert NRVO nur dann vollständig, wenn der Rückgabewert direkt verwendet wird, um eine Variable in der aufrufenden Methode zu initialisieren; andernfalls wäre es immer noch möglich, die Konstruktion des temporären Rückgabewertes zu verhindern, aber der Zuweisungsoperator für die Variable, der der Rückgabewert zugewiesen ist, könnte immer noch aufgerufen werden (siehe @Steve Jessops Kommentare für Details).
Matteo Italia 21.02.2011, 00:04
quelle
6

Etwas dynamisch zu erstellen ist eine schlechte Übung, es sei denn, es ist wirklich notwendig. Es gibt selten einen guten Grund, einen Container dynamisch zu erstellen, daher ist dies normalerweise keine gute Idee.

Bearbeiten: Gewöhnlich, anstatt sich über Dinge wie die schnelle oder langsame Rückgabe eines Containers Gedanken zu machen, sollte der meiste Code nur mit einem Iterator (oder zwei) in den Container gehen.

    
Jerry Coffin 21.02.2011 00:01
quelle
2

Das dynamische Erstellen von Objekten wird in C ++ als eine schlechte Methode angesehen. Was passiert, wenn eine Ausnahme von Ihrem "// ..." Code ausgelöst wird? Sie können das Objekt nie löschen. Es ist einfacher und sicherer, einfach zu tun:

%Vor%

Kürzere, sicherere, mehr straightforward ... Was die Leistung betrifft, werden moderne Compiler die Kopie bei der Rückkehr optimieren und wenn sie nicht in der Lage sind, werden Move Konstruktoren ausgeführt, so dass dies immer noch eine billige Operation ist.

>     
Nemanja Trifunovic 21.02.2011 00:00
quelle
0

Vielleicht beziehen Sie sich auf diese letzte Frage: C ++: vector & lt; string & gt ; * args = neuer Vektor & lt; string & gt; (); verursacht SIGABRT

Ein Liner: Es ist eine schlechte Übung, weil es ein Muster ist, das zu Speicherlecks neigt.

Sie zwingen den Aufrufer, die dynamische Zuweisung zu akzeptieren und die Lebensdauer zu übernehmen. Es ist mehrdeutig aus der Deklaration, ob der zurückgegebene Zeiger ein statischer Puffer ist, ein Puffer, der einer anderen API (oder einem anderen Objekt) gehört, oder ein Puffer, der jetzt dem Aufrufer gehört. Sie sollten dieses Muster in jeder Sprache vermeiden (auch in C), es sei denn, aus dem Funktionsnamen geht hervor, was gerade passiert (z. B. strdup, malloc).

Der übliche Weg ist stattdessen, dies zu tun:

%Vor%

Alle Objekte befinden sich auf dem Stapel und alle Löschvorgänge werden vom Compiler erledigt. Oder geben Sie einfach als Wert zurück, wenn Sie einen C ++ 0x-Compiler + STL ausführen oder die Kopie nicht stören.

    
John Ripley 21.02.2011 00:05
quelle
0

Ich mag Jerry Coffins Antwort. Wenn Sie außerdem vermeiden möchten, dass eine Kopie zurückgegeben wird, sollten Sie den Ergebniscontainer als Referenz übergeben und die Methode swap () wird möglicherweise manchmal benötigt.

%Vor%     
albert 21.02.2011 01:08
quelle
0

Programmieren ist die Kunst, gute Kompromisse zu finden. Dynamisch zugewiesener Speicher kann natürlich einen Platz haben, und ich kann sogar an Probleme denken, bei denen ein guter Kompromiss zwischen Code-Komplexität und Effizienz erreicht wird, indem std::vector<std::vector<T>*> verwendet wird.

Allerdings macht std::vector die meisten Anforderungen dynamisch zugewiesener Arrays sehr gut und verwaltete Zeiger sind oft nur eine perfekte Lösung für dynamisch zugewiesene Einzelinstanzen. Dies bedeutet, dass es nicht so häufig ist, Fälle zu finden, in denen ein nicht verwalteter dynamisch zugewiesener Container (oder tatsächlich alles, was tatsächlich zugewiesen wird) der beste Kompromiss in C ++ ist.

Das macht meiner Meinung nach die dynamische Zuordnung nicht "schlecht", sondern "verdächtig", wenn man es im Code sieht, denn es gibt eine hohe Wahrscheinlichkeit, dass bessere Lösungen möglich sind.

In Ihrem Fall zum Beispiel sehe ich keinen Grund für die dynamische Zuordnung; Die Funktion, die einen std :: vector zurückgibt, wäre effizient und sicher. Mit jedem vernünftigen Compiler wird die Rückgabewert-Optimierung verwendet, wenn Sie einem neu deklarierten Vektor zuweisen und wenn Sie das Ergebnis zuweisen müssen Zu einem bestehenden Vektor können Sie noch etwas tun wie:

%Vor%

das wird kein Kopieren der Daten, aber nur einige Zeiger drehen (beachten Sie, dass Sie nicht die offensichtlich natürlichere myvector.swap(FindPoints()) wegen einer C ++ Regel verwenden können, die manchmal lästig ist, verbietet Provisorien als nicht-const Referenzen).

Meiner Erfahrung nach sind die komplexesten Datenstrukturen, in denen die gleiche Instanz über mehrere Zugriffspfade erreicht werden kann, die größte Quelle für die Bedürfnisse dynamisch zugewiesener Objekte (zB sind Instanzen gleichzeitig in einer doppelt verknüpften Liste und durch eine Karte indiziert) . In der Standardbibliothek sind Container immer die only Eigentümer der enthaltenen Objekte (C ++ ist eine kopiersemantische Sprache), so dass es schwierig sein kann, diese Lösungen ohne den Zeiger und das dynamische Zuordnungskonzept effizient zu implementieren.

>

Oft können Sie vernünftige Kompromisse eingehen, die nur Standardcontainer verwenden (möglicherweise müssen Sie einige zusätzliche O (Log N) -Lookups bezahlen, die Sie hätten vermeiden können), und das kann angesichts des viel einfacheren Codes IMO der beste Kompromiss sein in den meisten Fällen.

    
6502 21.02.2011 06:51
quelle

Tags und Links