Betrachten Sie den folgenden Code:
%Vor% Ich habe einen kleinen Streit mit meinem Kollegen.
Welcher Destruktor sollte ptr_
löschen?
Ich denke, dass es in A
sein sollte, weil es dort ist, wo es zugeteilt wird.
Er denkt, dass es in Base
sein sollte, weil das der Zeiger ist.
Bitte geben Sie mögliche Fallstricke, warum eine Methode besser ist als die andere (wenn es eine gibt, und es ist nicht nur eine Frage des Geschmacks)
BEARBEITEN
Viele Leute stellen das Design in Frage. In der Praxis ist der Code viel komplizierter und modelliert ein Bildverarbeitungssystem mit ein paar Kameras.
Die Rolle von Base besteht darin, die Ressourcen des Systems zu verwalten (einige Konfigurationen existieren).
Die Klasse A
und andere abgeleitete Typen wie diese entscheiden über die Konfiguration von Base
und initialisieren das System für eine bestimmte Konfiguration. Ich denke, jetzt können Sie fragen, ob Vererbung die richtige Wahl gegenüber Komposition ist. A
ist -a Base
, nur eine bestimmte Art, und deshalb haben wir die Vererbung gewählt. In diesem Fall ist ptr_
ein Zeiger auf einen bestimmten Treiber einer Kamera, der vom abgeleiteten Typ bestimmt wird, weshalb er dort zugeordnet ist.
Sie machen sich Sorgen über Code-Duplikation, aber ich fürchte, es hat Ihr Urteil getrübt.
Ich weiß, dass TROCKEN viel Presse bekommen (und das aus gutem Grund), aber Sie haben es falsch verstanden. Es gibt einen Unterschied zwischen der Nicht-Duplizierung von code und nicht der Duplizierung von -Funktionalität .
DRY bedeutet nicht, Funktionalität zu duplizieren. Es mag Codemuster geben, die gleich aussehen, aber für verschiedene Ziele verwendet werden, diese sollten nicht zusammengeführt werden, da jedes Ziel unabhängig voneinander verschoben werden kann (und Unvereinigen ist ein Albtraum). Ihre Ähnlichkeit ist zufällig .
Das ist hier der Fall. Sie erzwingen willkürlich ein int* ptr_
in der Klasse Base
(die es nicht verwendet), weil sicherlich alle abgeleiteten Klassen es brauchen. Wie kannst du das Wissen ? Im Moment stellt sich heraus, dass alle abgeleiteten Klassen es brauchen, aber das ist ein Zufall. Sie könnten wählen, sich auf etwas anderes zu verlassen.
Daher sollte ein vernünftiger Entwurf eine Schnittstelle in der Basisklasse bereitstellen und es jeder abgeleiteten Klasse überlassen, ihre eigene Implementierung auszuwählen:
%Vor% Wenn andererseits if die Klasse Base
eine allgemeine Funktionalität bereitstellen sollte, die Datenelemente erfordert, dann könnten diese natürlich in der Base
-Klassen-Implementierung hochgezogen werden ... Es könnte jedoch argumentiert werden, dass dies eine vorzeitige Optimierung ist, da wiederum einige abgeleitete Klassen sich dafür entscheiden, die Dinge anders zu machen.
Jede Klasse sollte ihre eigenen Ressourcen verwalten. Wenn Sie mehrere abgeleitete Klassen haben, muss jeder daran denken, den Zeiger freizugeben. Das ist schlecht, da es Code dupliziert.
Wenn es einer abgeleiteten Klasse erlaubt ist, dem Zeiger keinen neuen Wert zuzuweisen, setzen Sie ihn im Basisklassenkonstruktor auf Null. Wenn eine abgeleitete Klasse dem Zeiger die Adresse einer automatischen Variablen zuweisen darf, überprüfen Sie Ihr Design.
Verwenden Sie Smartpointer, um das Problem vollständig zu beseitigen (wenn ich grep delete *
in meiner gesamten Codebasis finde, finde ich keine Übereinstimmung:)
Die Frage ist eine von Design mehr als einer der Implementierung. Ich stimme eher mit Ihrem Kollegen überein, wenn der Zeiger in der Basis ist, scheint es anzuzeigen, dass das Basisobjekt verantwortlich für die Verwaltung der Ressource und nicht für den abgeleiteten Typ ist.
Tatsächlich ist das merkwürdige Bit die Tatsache, dass der abgeleitete Konstruktor ein Basiselement modifiziert, es macht wahrscheinlich mehr Sinn, den Zeiger an den Basiskonstruktor zu übergeben, in welchem Fall die Semantik klarer wäre: die Basis erhält während der Bauphase eine Ressource und ist dafür verantwortlich, sie während ihrer Lebensdauer zu verwalten.
Wenn die Basis die Ressource nicht verwalten sollte, warum ist dann der Zeiger auf dieser Ebene? Warum ist es kein Mitglied des abgeleiteten Typs?
Beachten Sie, dass Sie beim Verwalten einer Ressource die Regel der drei swap
was praktisch ist).
Wenn Sie copy-constructor und Zuweisungsoperator zum Mix hinzugefügt haben, werden Sie wahrscheinlich feststellen, dass der natürliche Ort zum Verwalten der Ressource dort ist, wo sie sich befindet.
1 Die Regel ist generisch, da es gute Gründe gibt, sie nicht immer zu implementieren. Wenn Sie beispielsweise Ihre Ressourcen in Membern verwalten, die RAII implementieren (als Smart Pointer), müssen Sie möglicherweise copy-construction und assignment-operator wie für deep bereitstellen Semantik kopieren, aber Zerstörung wäre nicht erforderlich. Alternativ können Sie in einigen Kontexten deaktivieren * copy-construction * und / oder Zuweisungen .
Tatsächlich sollte ptr_
private
sein! (Siehe Scott Meyers Effective C ++ oder irgendeinen Primer zur Objektorientierung.)
Nun stellt sich die Frage nicht einmal selbst: Nur die Basisklasse kann ihre Ressource verwalten. Die abgeleitete Klasse kann sie nur initialisieren, indem sie eine Anfrage an ihre Basis weitergibt (vorzugsweise über den Konstruktor):
%Vor% Ich würde es vorziehen, es in der Basisklasse zu löschen, weil,
Wenn ich die Zuordnung in Abgeleiteten Klassendestruktoren (die vor dem Basisklassendestruktor aufgerufen werden) aufheben, besteht die Möglichkeit, dass sie versehentlich in der Basisklasse verwendet werden.
Auch die Lebensdauer des Zeigers endet innerhalb der Basisklasse, so dass der Platz für die Zuweisung sein sollte. In der Tat würde ich den Zeiger innerhalb der Basisklasse und nicht abgeleiteten Klasse zuordnen.
Am besten ist es natürlich, RAII zu verwenden, und den Zeiger nicht explizit zu delettieren. Lassen Sie die Smartpointers selbst die Zuweisung aufheben.
ptr_ sollte nicht in der Basisklasse sein, weil nichts in der Basisklasse es verwendet. Es sollte in der abgeleiteten Klasse sein.
Wenn Sie es dort haben müssen, stimme ich Ihnen zu, sollte es in den abgeleiteten Klassen desteuctor gelöscht werden. Die Basisklasse kann nicht einmal wissen, ob dieser Zeiger überhaupt verwendet wurde, warum sollte er gelöscht werden?
Für mich ist es das Beste, das gleiche Objekt für die Zuweisung und die Nichtzuweisung zu haben.
In deinem Code wäre also class A
verantwortlich für delete
, während new
ausgeführt wurde.
Aber: warum hat A
ptr_
anstatt Base
zugewiesen? Das ist die wirkliche Frage hier. Es gibt ein Designproblem für mich, da ptr_
zu Base
gehört. Entweder sollte es stattdessen zu A
gehören, oder Base
sollte für die Speicherverwaltung zuständig sein.
Ich würde dies tun, indem ich RAII verwende.
%Vor%Eine andere Möglichkeit wäre die Verwendung von std :: shared_ptr, die es erlaubt, den Lebensbereich von Objekten zwischen verschiedenen Objekten zu verwalten, was in Ihrem Fall der Fall ist (Base und A teilen die Notwendigkeit der Existenz des Objekts (int)). p>