2013-05-06 26 views
6

Also hier ist, was ich weiß:Ist die Kapselung über öffentliche const Zeiger eine gute Idee?

  • Es ist ratsam, nicht Ihre ivars direkt in Ihrem API aussetzen; Verwendung vielmehr Accessoren
  • Ein const Zeiger auf ein nicht const Objekt einfach bedeutet, dass Sie das Objekt verändern können, aber nicht umgeleitet, wo der Zeiger

Hier ist meine Situation:

Ich habe ein paar verwandte Klassen. Ich möchte eine einfache Klasse erstellen, die über Zusammensetzung diese in einer logischen Schnittstelle kombiniert. Jede meiner eingeschlossenen Klassen hat bereits eine öffentliche und private Unterscheidung in ihrer API, so dass es mir nichts ausmacht, sie direkt den Benutzern meiner Elternklasse auszusetzen. Dies bedeutet, dass es für mich Overkill wäre, Accessoren für diese Ivars zu schreiben, da die Klassen bereits verwalten, was öffentlich ist und was nicht. Aber ich möchte nicht, dass Benutzer die tatsächlichen Objekte ändern, die in diese zusammengesetzte Elternklasse eingeschlossen sind.

So ist die einzige Art, wie ich denken kann, dies zu tun ist const Zeiger auf diese Objekte zu verwenden, wie zum Beispiel:

class Parent{ 

    public: 

    EnclosedClass1*const ec1; //users can enjoy the public API of EnclosedClass 
    EnclosedClass2*const ec2; 

    void convenienceMethod(){ 
     //performs inter-related functions on both ec1 and ec2 
    } 

} 

Auf diese Weise gibt keinen Schaden jemand direkt mit der API von ec1 Schnittstelle in der Vermietung und ec2 aber ich möchte sicherstellen, dass dies ziemlich idiotensicher ist, dass zumindest diese Person nicht die tatsächliche Ressource manipulieren kann, daher die const Zeiger.

Macht das Sinn, ist es eine gute Verwendung von const Zeigern?

Alternativ könnte ich sie private komplett machen, vergessen Sie die Zeiger (diese Klasse verwaltet diese Objekte trotzdem), und nur die zusätzliche Arbeit zu schreiben, Accessoren für die öffentlichen Funktionen in diesen Objekten enthalten. Aber das scheint einfach übertrieben, oder?

+0

"die zusätzliche Arbeit ... scheint nur wie Overkill, oder?" Nein, es scheint die passende Lösung zu sein. – Caleb

+1

Es gibt einen Grund, warum andere Objekte dies nicht tun, und ein großer Teil davon besteht darin, die Schnittstelle von der Implementierung zu trennen. Ein Klassen Interna sind privat, so dass sie ändern können, ohne andere Teile der Anwendung zu brechen. – tadman

+0

siehe [diese verwandte Frage] (http://stackoverflow.com/q/14932675/819272) – TemplateRex

Antwort

6

Die traditionelle OOP-Methode besteht darin, Daten privat zu halten und öffentliche Zugriffsrechte zu haben, wenn man sie außerhalb der Klasse verwenden möchte.

Wenn Sie nicht möchten, dass die privaten Datenzeiger geändert werden, dann stellen Sie keine Setter bereit, sondern nur die Getter.

class Parent{ 

    private: 
     EnclosedClass1*const ec1; 
     EnclosedClass2*const ec2; 

    public: 
     EnclosedClass1* getEnclosedClass1() const {...}; 
     EnclosedClass2* getEnclosedClass2() const {...};  

     void convenienceMethod(){ 
    } 

}

eine andere Möglichkeit (wie @pmr in den Kommentaren unten schlägt) ist:

const EnclosedClass1& getEnclosedClass1() const {...}; 
const EnclosedClass2& getEnclosedClass2() const {...};  

mit const Verweis Sie den Benutzer schützen, den Wert durch die Getter zurück testen. Aber Sie müssen sicherstellen, dass Ihr Objektzustand konsistent ist und niemals interne Zeiger auf nullptr; In diesem Fall sollten Sie eine Ausnahme auslösen.

+0

Ok, verstehe, aber was ist der Vorteil der Verwendung von Getter in diesem Fall, anstatt nur die const-Zeiger zu verwenden? Scheint ähnliche Einschränkungen zuzulassen. – johnbakers

+0

Es ist nur die Gewohnheit, verhaltensbasierte Schnittstellenprogrammierung zu fördern. Es ist auch eine Möglichkeit, dem SOLID-Prinzip zu folgen: Demeter-Gesetz. http://en.wikipedia.org/wiki/Law_of_Demeter –

+4

Warum würden Sie Zeiger in Ihren Gettern zurückgeben, wenn niemand ihnen trotzdem zuweisen sollte? Warum nicht eine Referenz/Referenz auf const zurückgeben? – pmr

0

Wenn Sie eine Klasse haben, die zwei andere Objekte enthält und den Clients freien und vollständigen Zugriff auf diese Member bietet, sollten Sie darüber nachdenken, ob Ihre Klasse das richtige Design hat. Das Gesetz von demeter wird nicht ungültig, weil Sie keinen Getter verwenden, d. H. Parent.ec1-> foo ist nicht besser als parent.getEc1(). Foo.

In jedem Fall sind const Mitglieder in der Regel mehr Mühe, als sie wert sind.

1

Macht das Sinn? Ja.

Ist es eine gute Verwendung von const Zeigern? Nicht wirklich. Die kanonische Art des Codierens besteht darin, für jedes Element eine Getterfunktion zu haben und interne Daten privat zu machen. Wenn Sie einen anderen Weg gehen, werden sich die Leser Ihres Codes wundern, warum Sie das tun, da dies nicht Standard ist.

Also machen Sie Ihre const-Zeiger privat und erstellen Sie Getter, um auf sie zuzugreifen. Aus Leistungsgründen können Sie diese Getter inline d Funktionen machen, so dass es keinen Unterschied in der generierten ausführbaren Datei macht, aber die Lesbarkeit Ihres Codes erheblich verbessert.

2

Mit diesem Ansatz sind verschiedene Dinge nicht in Ordnung. Die erste ist, dass die Tatsache, dass die Mitglieder eine öffentliche und eine private Seite ihrer Schnittstelle haben, nichts vom Standpunkt der umschließenden Art bedeutet. Der vollständige Typ kann einen anderen Invariantensatz aufweisen, den Benutzer unterbrechen können, indem sie die beiden Unterobjekte unabhängig voneinander ändern.

Auch wenn der Typ Parent keine Invariante hat (d. H. Es hängt nicht vom Zustand der Unterobjekte ab), ist der vorgeschlagene Ansatz nicht const-korrekt. Bei einer const Parent& kann der Aufrufer jede Operation (const und nicht const) auf beide Unterobjekte anwenden, wodurch der Zustand des Objekts Parent effektiv geändert wird. Ein etwas besserer Ansatz in diesem Fall wäre die Subobjekte direkt zu speichern:

class Parent { 
public: 
    EnclosedClass1 ec1; 
    EnclosedClass2 ec2; 
... 

Aber ich würde empfehlen, dass Sie Accessoren liefern die Daten und Mutatoren (wenn das Wort macht Sinn) zu ändern, den Staat zu extrahieren der Objekte. Wenn Sie später noch Invarianten hinzufügen müssen, können Sie sie einfach dort stecken.

+0

Diese Antwort ist im allgemeinen Fall ziemlich interessant, aber da OP angegeben hat, dass der Zugriff schreibgeschützt sein sollte, gilt die übliche invariante Logik nicht wirklich. –

+0

@ AndréCaron: Vielleicht war ich nicht klar genug. Die Tatsache, dass die beiden Mitglieder öffentliche und private Schnittstellen haben, bedeutet nicht, dass jede Operation, die auf eine von beiden angewendet wird, garantieren wird, dass die Invarianten des vollständigen Typs beibehalten werden. Die vorgeschlagene Lösung hat konstante Zeiger auf * veränderbare * Objekte, was bedeutet, dass der Zustand jedes Objekts unabhängig modifizierbar wäre. Das Problem besteht nicht nur darin, auf ein anderes Objekt zu zeigen, sondern auch, dass sich der Zustand des spitzen Objekts außerhalb der Kontrolle des vollständigen Typs ändern kann. –

+0

Ja, ich habe das erste Mal verstanden. Und ich stimme zu, dass der vorgeschlagene Code falsch ist ("const pointer" statt "pointer to const"), aber OP hat ausdrücklich angegeben * "Ich möchte nicht, dass Benutzer die tatsächlichen Objekte ändern, die in diesen zusammengesetzten Eltern enthalten sind Klasse. "* –

0

einer der Gründe könnte sein, dass es von Constcast in non const Zeiger umgewandelt werden könnte.

0

Entscheiden Sie, was Ihre Klasse tun soll, und schreiben Sie eine Schnittstelle, die diese Dinge bietet. Wenn es wichtig ist, Benutzern zu erlauben, sich mit internen Daten zu befassen, dann schreiben Sie Schnittstellen, um das zu tun. In den meisten Fällen stellt eine Klasse jedoch eine Abstraktion dar, die sich von der Summe der Dinge unterscheidet, die Sie mit ihren internen Daten ausführen können. Daher sollte die Schnittstelle nicht interne Daten verfügbar machen. Beispielsweise implementiert eine Klasse, die Namen von Schülern enthält, diese in der Regel als eine Art Container, der std::string Daten enthält. Diese Klasse sollte nicht einen Zeiger oder Verweis auf die Zeichenfolge für den Namen einer Person bereitstellen. Wenn ein Name korrigiert werden soll, sollte dies beispielsweise in einer separaten Editor-Klasse geschehen; einmal korrigiert, kann der gespeicherte Name mit der neuen Version aktualisiert werden. Die Klasse muss die gesamte std::string Schnittstelle nicht für Benutzer freigeben.

Verwandte Themen