Konventionen für Accessor-Methoden (Getter und Setter) in C ++

78

In SO wurden einige Fragen zu Accessor-Methoden in C ++ gestellt, aber keine konnte meine Neugier auf das Problem befriedigen.

Ich versuche, Accessoren nach Möglichkeit zu meiden, weil ich, wie Stroustrup und andere berühmte Programmierer, eine Klasse mit vielen von ihnen als Zeichen einer schlechten OO betrachte. In C ++ kann ich in den meisten Fällen einer Klasse mehr Verantwortung hinzufügen oder das Schlüsselwort friend verwenden, um sie zu vermeiden. In einigen Fällen benötigen Sie jedoch wirklich Zugriff auf bestimmte Klassenmitglieder.

Es gibt mehrere Möglichkeiten:

1. Verwenden Sie überhaupt keine Accessoren

Wir können nur die jeweiligen Mitgliedsvariablen veröffentlichen. Dies ist ein No-Go in Java, scheint aber mit der C ++ - Community in Ordnung zu sein. Ich bin jedoch etwas besorgt über Fälle, in denen eine explizite Kopie oder ein schreibgeschützter (const) Verweis auf ein Objekt zurückgegeben werden sollte. Ist das übertrieben?

2. Verwenden Sie get / set-Methoden im Java-Stil

Ich bin mir nicht sicher, ob es überhaupt von Java stammt, aber ich meine das:

int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount

3. Verwenden Sie objektive Get / Set-Methoden im C-Stil

Das ist ein bisschen komisch, aber anscheinend immer häufiger:

int amount(); // Returns the amount
void amount(int amount); // Sets the amount

Damit dies funktioniert, müssen Sie einen anderen Namen für Ihre Mitgliedsvariable finden. Einige Leute fügen einen Unterstrich hinzu, andere stellen "m_" voran. Ich mag es auch nicht.

Welchen Stil benutzt du und warum?

Noarth
quelle
5
Abgesehen von kleinen Nur-Daten-Strukturen, was hat Ihnen den Eindruck vermittelt, dass öffentliche Mitgliedsvariablen in C ++ in Ordnung wären? Es ist normalerweise auch ein No-Go.
Georg Fritzsche
1
Ja, es ist auch in C ++ ein No-Go. Wenn Sie jedoch mit MFC arbeiten, können Sie davon ausgehen, dass dies in Ordnung ist.
Objekt
Zuletzt diskutiert als [set / get Methoden in C ++] ( stackoverflow.com/questions/3632533 ), die ich mit dem serach "c ++ getter setter" gefunden habe . Siehe auch [Getter und Setter, Zeiger oder Referenzen und gute Syntax für C ++? ] ( stackoverflow.com/questions/1596432 ), C ++ - Getter / Setter-Codierungsstil und wahrscheinlich andere.
dmckee --- Ex-Moderator Kätzchen
4
In diesem Artikel (PDF) erfahren Sie, warum Getter / Setter einfach falsch sind .
sbi
1
@vines: In echtem OO sollten sich die Schnittstellen eines Objekts (definiert in einer Klasse in statisch typisierten Sprachen) nicht mit dem Status des Objekts befassen, sondern nur mit den Operationen, die das Objekt bereitstellt. Dass diese Operationen den Status des Objekts ändern, ist ein Implementierungsdetail.
sbi

Antworten:

44

Aus meiner Sicht als mit 4 Millionen Zeilen C ++ - Code (und das ist nur ein Projekt) aus Wartungssicht würde ich sagen:

  • Es ist in Ordnung, keine Getter / Setter zu verwenden, wenn Mitglieder unveränderlich (dh const) oder einfach ohne Abhängigkeiten sind (wie eine Punktklasse mit Mitgliedern X und Y).

  • Wenn nur ein Mitglied ist, ist privatees auch in Ordnung, Getter / Setter zu überspringen. Ich zähle auch Mitglieder interner Zuhälterklassen, als privateob die .cpp-Einheit kleiner wäre.

  • Wenn ein Mitglied publicoder protected( protectedgenauso schlecht wie public) und nicht const, nicht einfach oder abhängig ist, verwenden Sie Getter / Setter.

Als Wartungsmann ist mein Hauptgrund, warum ich Getter / Setter haben möchte, der, dass ich dann einen Platz habe, an dem ich Haltepunkte / Protokollierung / etwas anderes setzen kann.

Ich bevorzuge den Stil von Alternative 2., da dieser durchsuchbarer ist (eine Schlüsselkomponente beim Schreiben von wartbarem Code).

FuleSnabel
quelle
2
Das Akzeptieren einer bestimmten Antwort auf Community-Wiki-Fragen ist immer etwas seltsam, da jede Antwort hilft. Ich denke jedoch, dass Ihre am besten gerechtfertigt ist. Ich versuche derzeit, Getter / Setter-Methoden nach Möglichkeit zu vermeiden. Es dauert einige Zeit, um Alternativen zu finden, aber sie sind oft besser. Wenn ich einfache Daten benötige, wie von Ihnen erwähnt, verwende ich normalerweise eine reine Datenstruktur.
Noarth
Wenn Sie Accessoren debuggen müssen, ist dies nur ein weiterer Grund, sie nicht zu verwenden. Sicher gibt es interessantere Orte, um einen Haltepunkt zu setzen.
Hoodaticus
1
Dies ist ein guter Punkt, um darauf hinzuweisen, dass es beim Debuggen nützlich ist, aber ich stimme zu, dass es wahrscheinlich alternative Möglichkeiten gibt, diese Informationen zu erhalten. Abgesehen davon, dass meine Gedanken GETS & SETS sind, dürfen sie niemals zusammen verwendet werden. Viele schlagen vor, dass Sie mit GET / SET den Rückgabewert ändern können, aber dann ist es kein GET / SET, sondern eine Methode, die eine Reihe von Aktionen ausführt, und sollte so benannt werden, dass sie sich voll und ganz auf den jeweiligen Prozess konzentriert.
Jeremy Trifilo
9

2) ist die beste IMO, weil es Ihre Absichten am klarsten macht. set_amount(10)ist sinnvoller als amount(10)und als netter Nebeneffekt erlaubt ein Mitglied namens amount.

Öffentliche Variablen sind normalerweise eine schlechte Idee, da es keine Kapselung gibt. Angenommen, Sie müssen einen Cache aktualisieren oder ein Fenster aktualisieren, wenn eine Variable aktualisiert wird. Schade, wenn deine Variablen öffentlich sind. Wenn Sie eine festgelegte Methode haben, können Sie diese dort hinzufügen.

AshleysBrain
quelle
2
Die kanonische Kapselung bedeutet, dass interne Zustands- und Verhaltensdetails einer logischen Einheit ausgeblendet werden. Wenn Sie über die öffentliche Schnittstelle auf ein privates Datenelement zugreifen, wird die Kapselung bereits unterbrochen, unabhängig davon, ob Sie dies mit Accessoren tun. Gut gekapselte Klassen haben keine Accessoren, sondern aktive Methoden, die den internen Zustand auf öffentlich unsichtbare Weise nutzen und beeinflussen.
Ulidtko
1
In Bezug auf das Szenario "Geänderte Anforderungen", z. B. Mutieren des Caches oder Aktualisieren der Benutzeroberfläche oder Senden einer Netzwerkanforderung innerhalb eines getId()Aufrufs: Indem Sie diese Logik in einen Getter / Setter einfügen, verletzen Sie den Vertrag der Accessor-Methode. Sie werden die Client-Code-Kompilierung nicht auf diese Weise unterbrechen, aber Sie werden sicherlich die Laufzeitoperation unterbrechen, was noch schlimmer ist. Weil niemand in einem vernünftigen Geisteszustand davon ausgehen wird, dass Ihr Getter beispielsweise manchmal eine Speicherzuweisung vornehmen oder Ihr Setter in ein Dateisystem schreiben könnte. In all diesen Szenarien müssen Sie Redesign für die neuen Anforderungen.
Ulidtko
# 1 macht Ihre Absichten am klarsten, nicht # 2. # 2 gibt vor, den internen Status zu kapseln, indem eine Variable privat gemacht wird, und macht dann seine gesamte Arbeit rückgängig, um diesen Status zu verbergen, indem es jedem über die öffentlichen Zugriffsmethoden ermöglicht wird, diese Variable zu lesen und zu schreiben. # 2 ist kein Fall klarer Absichten, es ist nur ein Shell-Spiel.
Hoodaticus
7
  1. Ich benutze diesen Stil nie. Weil es die Zukunft Ihres Klassendesigns einschränken kann und explizite Getter oder Setter mit guten Compilern genauso effizient sind.

    In der Realität erzeugen explizite Inline-Getter oder -Setter natürlich ebenso viel zugrunde liegende Abhängigkeit von der Klassenimplementierung. Sie reduzieren nur die semantische Abhängigkeit. Sie müssen immer noch alles neu kompilieren, wenn Sie sie ändern.

  2. Dies ist mein Standardstil, wenn ich Zugriffsmethoden verwende.

  3. Dieser Stil scheint mir zu "klug". Ich benutze es in seltenen Fällen, aber nur in Fällen, in denen ich wirklich möchte, dass sich der Accessor so gut wie möglich wie eine Variable fühlt.

Ich denke, es gibt einen Fall für einfache Taschen mit Variablen mit möglicherweise einem Konstruktor, um sicherzustellen, dass sie alle auf etwas Vernünftiges initialisiert sind. Wenn ich das mache, mache ich es einfach zu einem structund lasse alles öffentlich.

Omnifarious
quelle
4
+1 für die "zu klug". Ich würde empfehlen, die Überlastungsmöglichkeit nicht zu missbrauchen, da dies die Leser nur erschwert und es auch schwieriger ist, die Codebasis zu durchsuchen.
Matthieu M.
-1 Für "Einschränkung der Zukunft Ihres Klassendesigns usw.". Sie entwerfen die Klasse so, wie sie jetzt ist. Wenn Sie es später neu gestalten möchten, tun Sie dies. Was ist, wenn dieses Feld in Zukunft verschwindet? Der Accessor hilft Ihnen nicht weiter. Und wenn Sie es behalten und einen Junk-Wert zurückgeben, haben Sie möglicherweise einen stillen Bruch, den Sie nicht kennen. Überdesignen Sie nicht, sonst könnten Sie einfach eine Karte mit Feldnamen-zu-Wert-Accessoren haben und damit fertig sein.
Einpoklum
Warum soll "der Accessor sich so weit wie möglich wie eine Variable fühlen"? Sie sind keine Variablen. Warum sollten Sie jemanden verwirren, um zu glauben, dass sie es sind?
Deetz
@deetz - Nun, @ property scheint in Python sehr beliebt zu sein. Auf diese Weise kann Unsichtbarkeit den Variablenzugriff in einen Funktionsaufruf verwandeln.
Omnifarious
6
  1. Das ist ein guter Stil, wenn wir nur pureDaten darstellen wollen.

  2. Ich mag es nicht :) weil get_/set_es wirklich unnötig ist, wenn wir sie in C ++ überladen können.

  3. STL verwendet diesen Stil wie std::streamString::strund std::ios_base::flags, außer wenn dies vermieden werden sollte! wann? Wenn der Name der Methode mit dem Namen eines anderen Typs in Konflikt steht, wird der get_/set_Stil verwendet, z. B. std::string::get_allocatorwegen std::allocator.

Sadeq
quelle
4
Ich befürchte, dass STL in dieser Hinsicht etwas inkonsistent sein könnte. Leider scheint Stroustrup selbst keine Meinung darüber geäußert zu haben, wie man Getter / Setter macht, außer dass er Klassen mit vielen von ihnen nicht mag.
Noarth
4

Im Allgemeinen halte ich es für keine gute Idee, zu viele Getter und Setter von zu vielen Entitäten im System zu verwenden. Dies ist nur ein Hinweis auf ein schlechtes Design oder eine falsche Kapselung.

Wenn jedoch ein solches Design überarbeitet werden muss und der Quellcode verfügbar ist, würde ich lieber das Visitor Design-Muster verwenden. Der Grund ist:

ein. Es gibt einer Klasse die Möglichkeit zu entscheiden, wem der Zugang zu ihrem privaten Staat gewährt werden soll

b. Es gibt einer Klasse die Möglichkeit zu entscheiden, welchen Zugang jeder der an ihrem privaten Staat interessierten Einheiten gewährt werden soll

c. Es dokumentiert einen solchen externen Zugriff eindeutig über eine übersichtliche Klassenschnittstelle

Grundidee ist:

a) Wenn möglich anders gestalten,

b) Refactor so, dass

  1. Der gesamte Zugriff auf den Klassenstatus erfolgt über eine bekannte individualistische Schnittstelle

  2. Es sollte möglich sein, für jede dieser Schnittstellen eine Art von Do's und Don'ts zu konfigurieren, z. B. sollte jeder Zugriff von einer externen Entität GUT erlaubt sein, jeder Zugriff von einer externen Entität BAD sollte nicht erlaubt sein und eine externe Entität OK sollte erlaubt sein, aber nicht gesetzt (zum Beispiel)

Chubsdad
quelle
Interessanter Punkt, keine Ahnung, warum dies abgelehnt wurde. Aber ich denke, ich würde das Schlüsselwort friend bevorzugen, wenn eine bestimmte Klasse Zugriff auf bestimmte Mitglieder benötigen würde.
fhd
2
  1. Ich würde Accessoren nicht von der Verwendung ausschließen. Mai für einige POD-Strukturen, aber ich halte sie für eine gute Sache (einige Accessoren haben möglicherweise auch zusätzliche Logik).

  2. Die Namenskonvention spielt keine Rolle, wenn Sie in Ihrem Code konsistent sind. Wenn Sie mehrere Bibliotheken von Drittanbietern verwenden, verwenden diese möglicherweise ohnehin unterschiedliche Namenskonventionen. Es ist also Geschmackssache.

Cătălin Pitiș
quelle
1

Eine zusätzliche Möglichkeit könnte sein:

int& amount();

Ich bin nicht sicher, ob ich es empfehlen würde, aber es hat den Vorteil, dass die ungewöhnliche Notation Benutzer davon abhalten kann, Daten zu ändern.

str.length() = 5; // Ok string is a very bad example :)

Manchmal ist es vielleicht nur die gute Wahl:

image(point) = 255;  

Eine andere Möglichkeit ist wiederum die funktionale Notation, um das Objekt zu ändern.

edit::change_amount(obj, val)

Auf diese Weise kann die Gefahr- / Bearbeitungsfunktion in einem separaten Namespace mit eigener Dokumentation entfernt werden. Dieser scheint natürlich mit generischer Programmierung zu kommen.

log0
quelle
Ich habe dies nicht abgelehnt, aber die allererste ist eine sehr schlechte Antwort, da sie Designdetails enthüllt, die ein normaler Accessor versteckt hält. Und der andere Vorschlag lohnt sich nicht, um die Antwort einzulösen. Wenn Sie so etwas wie den ersten Stil versuchen möchten, geben Sie zumindest eine Art Objekt zurück, das operator =anstelle eines Verweises auf ein internes Datenelement enthält.
Omnifarious
Um die Dinge klar zu machen, vervollständigt meine Antwort nur die Liste der Möglichkeiten, die das OP gestartet hat (ich sollte vielleicht einen Kommentar haben). Ich empfehle diese Möglichkeiten nicht besonders. Wie Omnifarious sagte, ist die Verwendung einer Überladung einer Proxy-Klasse operator=im ersten Fall offensichtlich zumindest eine gute Sache. In einigen Fällen würde ich die zweite verwenden, wenn ich Bearbeitungsfunktionen quer zur Klassenhierarchie bereitstellen möchte und wenn es sinnvoll ist, diese Funktionen klar von der Klasse zu trennen. Es ist wahrscheinlich eine schlechte Idee für einen einfachen Setter.
Log0
1

Ich habe die Idealisierung von Klassen anstelle von integralen Typen gesehen, um auf aussagekräftige Daten zu verweisen.

Etwas wie dieses unten nutzt C ++ - Eigenschaften im Allgemeinen nicht gut aus:

struct particle {
    float mass;
    float acceleration;
    float velocity;
} p;

Warum? Denn das Ergebnis von p.mass * p.acceleration ist ein Float und keine Kraft wie erwartet.

Die Definition von Klassen einen Zweck zu bezeichnen (auch wenn es ein Wert ist, wie Menge bereits erwähnt) macht mehr Sinn, und es uns ermöglichen , etwas zu tun:

struct amount
{
    int value;

    amount() : value( 0 ) {}
    amount( int value0 ) : value( value0 ) {}
    operator int()& { return value; }
    operator int()const& { return value; }
    amount& operator = ( int const newvalue )
    {
        value = newvalue;
        return *this;
    }
};

Sie können vom Operator int implizit auf den Betragswert zugreifen. Außerdem:

struct wage
{
    amount balance;

    operator amount()& { return balance; }
    operator amount()const& { return balance; }
    wage& operator = ( amount const&  newbalance )
    {
        balance = newbalance;
        return *this;
    }
};

Getter / Setter-Nutzung:

void wage_test()
{
    wage worker;
    (amount&)worker = 100; // if you like this, can remove = operator
    worker = amount(105);  // an alternative if the first one is too weird
    int value = (amount)worker; // getting amount is more clear
}

Dies ist ein anderer Ansatz, bedeutet nicht, dass er gut oder schlecht ist, sondern anders.

gus
quelle
0

Lassen Sie mich Ihnen eine zusätzliche Möglichkeit nennen, die am gewissenhaftesten erscheint.

Müssen lesen und ändern

Deklarieren Sie diese Variable einfach als öffentlich:

class Worker {
public:
    int wage = 5000;
}

worker.wage = 8000;
cout << worker.wage << endl;

Muss nur lesen

class Worker {
    int _wage = 5000;
public:
    inline int wage() {
        return _wage;
    }
}

worker.wage = 8000; // error !!
cout << worker.wage() << endl;

Der Nachteil dieses Ansatzes besteht darin, dass Sie den gesamten aufrufenden Code ändern müssen (dh Klammern hinzufügen), wenn Sie das Zugriffsmuster ändern möchten.

Rok Kralj
quelle
0

Variation von # 3, mir wurde gesagt, dass dies ein "fließender" Stil sein könnte

class foo {
  private: int bar;
  private: int narf;
  public: foo & bar(int);
  public: int bar();
  public: foo & narf(int);
  public: int narf();
};

//multi set (get is as expected)
foo f; f.bar(2).narf(3);
user13985481
quelle
Dies ist in Bezug auf die Funktionalität in Ordnung, geht jedoch etwas über den Rahmen der Frage hinaus. Dies ist im Grunde der Getter / Setter im C # -Stil mit der Fähigkeit, Sets zu verketten. Ich habe gehört, dass dieses Muster nicht oft verwendet wird und ich weiß, dass es sicherlich leicht austauschbar ist. In der Frage wird auch nach einer Erklärung gefragt, warum Sie dieses Muster gegenüber anderen verwenden, wenn Sie eines geben möchten.
Matias Chara