Ich denke, ich habe eine beträchtliche Erfahrung mit normalen (funktionalen) entworfenen Mustern, wie z. in die Bande von vier Buch , die ich hauptsächlich in verwendet Java und C #. In diesen "verwalteten" Sprachen ist dies alles, was Sie wissen müssen, um Ihre Arbeit zu erledigen.
In C ++ - Welt hat der Entwickler jedoch auch die Kontrolle darüber, wie alle Objekte zugewiesen, weitergegeben und gelöscht werden. Ich verstehe die Prinzipien (ich lese Stroutrup unter anderen Texten), aber es erfordert immer noch viel Mühe, mich zu entscheiden welcher Mechanismus für ein gegebenes Szenario am besten geeignet ist - und hier ist ein Portfolio speicherbezogener Entwurfsmuster sinnvoll.
Zum Beispiel musste ich gestern eine Klasse Results
erstellen, die ein Container für einige Objekte und eine Sammlung (in diesem Fall std :: vector) eines anderen Objekttyps war. Es gibt also ein paar Designfragen, die ich nicht wirklich beantworten konnte:
Natürlich sind intelligente Zeiger cool und was nicht, aber sie erzeugen syntaktisches Durcheinander und ich bin nicht überzeugt, ob die Verwendung von malloc für jedes einzelne Objekt optimal ist.
Ich wäre dankbar für Antworten auf die oben genannten Punkte, aber noch mehr für längere und allgemeinere Texte zu gedächtnisbezogenen Entwurfsmustern - damit ich die Probleme lösen kann, die ich auch montags haben werde!
>Die Antwort auf all Ihre Fragen endet ein und dasselbe: Es hängt davon ab, ob Sie Referenzsemantik oder Wertesemantik benötigen (mit einigen Vorbehalten ) berücksichtigen).
Wenn Sie eine Referenzsemantik benötigen, die standardmäßig in Sprachen wie Java und C # für UDTs (benutzerdefinierte Datentypen) mit dem Schlüsselwort class
definiert ist, dann haben Sie um für intelligente Zeiger zu gehen. In diesem Szenario möchten Sie, dass mehrere Clients safe-Aliase für ein bestimmtes Objekt halten, wobei das Wort safe diese beiden Anforderungen kapselt:
Dies ist, was intelligente Zeiger tun. Wenn Sie eine Referenzsemantik benötigen (und wenn Ihre Algorithmen nicht so sind, dass der Overhead der Referenzzählung signifikant ist, wenn eine gemeinsame Eigentümerschaft benötigt wird), dann sollten Sie Smartpointer verwenden .
Sie benötigen Referenzsemantik, wenn Sie zum Beispiel möchten, dass dasselbe Objekt Teil mehrerer Sammlungen ist. Wenn Sie das Objekt in einer Sammlung aktualisieren, sollen die Darstellungen desselben Objekts in allen anderen Sammlungen konsistent aktualisiert werden. In diesem Fall speichern Sie intelligente Zeiger auf Ihre Objekte in diesen Sammlungen. Intelligente Zeiger kapseln die Identität eines Objekts und nicht dessen Wert.
Wenn Sie jedoch keine Aliase erstellen müssen, sollten Sie sich auf die Wertesemantik verlassen. Dies ist, was Sie standardmäßig in C ++ erhalten, wenn Sie ein Objekt mit automatischem Speicher (d. H. Auf dem Stapel) deklarieren.
Zu beachten ist, dass STL-Sammlungen -Werte speichern. Wenn Sie also ein vector<T>
haben, werden Kopien von T
in Ihrem% co_de gespeichert %. Wenn Sie davon ausgehen, dass Sie keine Referenzsemantik benötigen, kann dies zu einem Overhead werden, wenn Ihre Objekte groß und teuer zu kopieren sind.
Um die Wahrscheinlichkeit dieses Szenarios einzuschränken, enthält C ++ 11 move -Operationen, die es ermöglichen, Objekte effizient nach Wert zu übertragen, wenn die alte Kopie des Objekts nicht mehr benötigt wird.
Ich werde jetzt versuchen, die obigen Konzepte zu verwenden, um Ihre Fragen direkter zu beantworten.
1) Soll ich diese Klasse nach Wert oder mit einem intelligenten Zeiger zurückgeben?
Es hängt davon ab, ob Sie Referenzsemantik benötigen oder nicht. Was macht die Funktion mit diesem Objekt? Wird das von dieser Funktion zurückgegebene Objekt von vielen Clients freigegeben? Wenn ja, dann durch intelligenten Zeiger. Wenn nicht, ist es möglich, einen effizienten Bewegungsvorgang zu definieren (dies ist fast immer der Fall)? Wenn ja, dann nach Wert. Wenn nicht, durch intelligenten Zeiger.
2) Sollten innerhalb der Klasse der Vektor und die Objekte normale Elemente sein oder sollten sie wieder als intelligente Zeiger gespeichert werden?
Sehr wahrscheinlich als normale Mitglieder, da Vektoren normalerweise begrifflich ein Teil Ihres Objekts sind und ihre Lebensdauer daher an die Lebensdauer des Objekts gebunden ist, das sie einbettet. In solch einem Szenario möchten Sie selten Referenzsemantik, aber wenn Sie dies tun, dann verwenden Sie intelligente Zeiger.
3) Soll ich die Objekte im Vektor direkt oder mit intelligenten Zeigern darauf speichern?
Gleiche Antwort wie für Punkt 1): Müssen Sie diese Objekte teilen? Solltest du Aliase für diese Objekte speichern? Möchten Sie, dass Änderungen an diesen Objekten in verschiedenen Teilen Ihres Codes angezeigt werden, die auf diese Objekte verweisen? Wenn ja, dann benutze geteilte Zeiger. Wenn nicht, ist es möglich, diese Objekte effizient zu kopieren und / oder zu verschieben? Wenn ja (meistens), Werte speichern. Wenn nicht, speichern Sie Smartpointer.
4) Was sollten die in meiner Ergebnisklasse definierten Getter (d. h. Werte, Referenzen oder intelligente Zeiger)?
?Gleiche Antwort wie für Punkt 2): Es hängt davon ab, was Sie mit den zurückgegebenen Objekten tun wollen: Möchten Sie, dass sie von vielen Teilen Ihres Codes gemeinsam genutzt werden? Wenn dies der Fall ist, geben Sie einen intelligenten Zeiger zurück. Wenn sie ausschließlich im Besitz von nur einem Teil sind, erfolgt eine Rückgabe nach Wert, es sei denn, das Verschieben / Kopieren dieser Objekte ist zu teuer oder überhaupt nicht erlaubt (ziemlich unwahrscheinlich). In diesem Fall gib einen intelligenten Zeiger zurück.
Beachten Sie bitte, dass Smartpointer in C ++ ein wenig kniffliger sind als Java / C # -Referenzen: Sie haben zwei Hauptargumente von Smartpointern, je nachdem, ob shareh ownership ist ( vector
) oder eindeutiger Besitz ( shared_ptr
) ist erwünscht. Zweitens müssen Sie zirkuläre Referenzen von unique_ptr
vermeiden, die Inseln von Objekten erzeugen würden, die sich gegenseitig am Leben erhalten, obwohl sie durch Ihren laufenden Code nicht mehr erreichbar sind. Dies ist der Grund, warum schwache Zeiger ( shared_ptr
) existieren.
Diese Konzepte führen natürlich zum Konzept der Verantwortung zur Verwaltung der Lebensdauer eines Objekts oder (allgemeiner) zur Verwaltung einer verwendeten Ressource. Vielleicht möchten Sie zum Beispiel über das RAII-Idiom lesen (Initialisierung der Ressourcenerfassung) und über die Ausnahmebehandlung im Allgemeinen (das Schreiben von ausnahmesicherem Code ist einer der Hauptgründe, warum diese Techniken existieren).
Tags und Links c++ design-patterns memory-management