Hat C ++ 11 Eigenschaften im C # -Stil?

93

In C # gibt es einen schönen Syntaxzucker für Felder mit Getter und Setter. Außerdem mag ich die automatisch implementierten Eigenschaften, mit denen ich schreiben kann

public Foo foo { get; private set; }

In C ++ muss ich schreiben

private:
    Foo foo;
public:
    Foo getFoo() { return foo; }

Gibt es ein solches Konzept in C ++ 11, das es mir ermöglicht, etwas Syntaxzucker dafür zu haben?

Radim Vansa
quelle
62
Es könnte mit ein paar Makros gemacht werden. rennt beschämt davon
Mankarse
7
@Eloff: Nur alles öffentlich zu machen ist IMMER eine schlechte Idee.
Kaiserludi
8
Es gibt kein solches Konzept! Und Sie brauchen es auch nicht: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout
2
a) Diese Frage ist ziemlich alt. b) Ich habe nach Syntaxzucker gefragt, damit ich die Klammern entfernen kann. c) obwohl der Artikel gültige Argumente gegen die Anpassung von Eigenschaften enthält, unabhängig davon, ob C ++ Eigenschaften benötigt oder nicht ist sehr subjektiv. C ++ ist auch ohne sie Touring-Maschinen-Äquivalent, aber das bedeutet nicht, dass ein solcher Syntaxzucker C ++ produktiver machen würde.
Radim Vansa
3
Definitiv nicht.

Antworten:

86

In C ++ können Sie Ihre eigenen Funktionen schreiben. Hier ist ein Beispiel für die Implementierung von Eigenschaften mit unbenannten Klassen. Wikipedia-Artikel

struct Foo
{
    class {
        int value;
        public:
            int & operator = (const int &i) { return value = i; }
            operator int () const { return value; }
    } alpha;

    class {
        float value;
        public:
            float & operator = (const float &f) { return value = f; }
            operator float () const { return value; }
    } bravo;
};

Sie können Ihre eigenen Getter und Setter an Ort und Stelle schreiben. Wenn Sie Zugriff auf Mitglieder der Inhaberklasse wünschen, können Sie diesen Beispielcode erweitern.

psx
quelle
1
Irgendwelche Ideen, wie Sie diesen Code so ändern können, dass noch eine private Mitgliedsvariable vorhanden ist, auf die Foo intern zugreifen kann, während die öffentliche API nur die Eigenschaft verfügbar macht? Ich könnte Foo natürlich nur zu einem Freund von Alpha / Beta machen, aber dann müsste ich immer noch alpha.value schreiben, um auf den Wert zuzugreifen, aber ich würde es vorziehen, wenn der direkte Zugriff auf die Mitgliedsvariable von innerhalb von Foo eher so wäre Zugriff auf ein Mitglied von Foo selbst und nicht auf ein Mitglied einer speziellen verschachtelten Eigenschaftsklasse.
Kaiserludi
1
@Kaiserludi Ja: In diesem Fall machen Sie Alpha und Bravo privat. In Foo können Sie mit den oben genannten "Eigenschaften" lesen / schreiben, aber außerhalb von Foo wäre dies nicht mehr möglich. Um dies zu umgehen, machen Sie eine konstante Verweisreferenz, die öffentlich ist. Es ist von außen zugänglich, aber nur zum Lesen, da es eine ständige Referenz ist. Die einzige Einschränkung ist, dass Sie einen anderen Namen für die öffentliche const-Referenz benötigen. Ich persönlich würde _alphafür die private Variable und alphaals Referenz verwenden.
Kapichu
1
@Kapichu: aus 2 Gründen keine Lösung. 1) In C # -Eigenschaften werden Getter / Setter häufig verwendet, um Sicherheitsüberprüfungen einzubetten, die öffentlichen Benutzern der Klasse auferlegt werden, während die Mitgliedsfunktionen direkt auf den Wert zugreifen können. 2) const-Referenzen sind nicht frei: Je nach Compiler / Plattform werden sie vergrößert sizeof(Foo).
Ceztko
@psx: Aufgrund der Einschränkungen dieses Ansatzes würde ich ihn vermeiden und auf die ordnungsgemäße Ergänzung des Standards warten, falls er jemals erscheinen sollte.
Ceztko
@Kapichu: Aber Alpha und Bravo im Beispielcode sind die Eigenschaften. Ich möchte direkt aus der Implementierung von Foo heraus auf die Variable selbst zugreifen, ohne eine Eigenschaft verwenden zu müssen, während ich den Zugriff nur über eine Eigenschaft in der API verfügbar machen möchte.
Kaiserludi
53

In C ++ ist dies nicht integriert. Sie können eine Vorlage definieren , um die Funktionalität der Eigenschaften nachzuahmen:

template <typename T>
class Property {
public:
    virtual ~Property() {}  //C++11: use override and =default;
    virtual T& operator= (const T& f) { return value = f; }
    virtual const T& operator() () const { return value; }
    virtual explicit operator const T& () const { return value; }
    virtual T* operator->() { return &value; }
protected:
    T value;
};

So definieren Sie eine Eigenschaft :

Property<float> x;

Um einen benutzerdefinierten Getter / Setter zu implementieren, erben Sie einfach:

class : public Property<float> {
    virtual float & operator = (const float &f) { /*custom code*/ return value = f; }
    virtual operator float const & () const { /*custom code*/ return value; }
} y;

So definieren Sie eine schreibgeschützte Eigenschaft :

template <typename T>
class ReadOnlyProperty {
public:
    virtual ~ReadOnlyProperty() {}
    virtual operator T const & () const { return value; }
protected:
    T value;
};

Und um es im UnterrichtOwner zu benutzen :

class Owner {
public:
    class : public ReadOnlyProperty<float> { friend class Owner; } x;
    Owner() { x.value = 8; }
};

Sie können einige der oben genannten Punkte in Makros definieren , um sie übersichtlicher zu gestalten.

Michael Litvin
quelle
Ich bin gespannt, ob dies zu einer kostengünstigen Funktion kompiliert wird. Ich weiß nicht, ob das Umschließen jedes Datenelements in eine Klasseninstanz beispielsweise zu derselben Art von Strukturpackung führt.
Dai
1
Die "benutzerdefinierte Getter / Setter" -Logik könnte durch die Verwendung von Lambda-Funktionen syntaktisch sauberer gemacht werden. Leider können Sie ein Lambda außerhalb eines ausführbaren Kontexts in C ++ (noch!) Nicht definieren. Ohne Verwendung eines Präprozessor-Makros erhalten Sie also Code, der gerecht ist so fummelig wie dumme Getter / Setter, was unglücklich ist.
Dai
2
Die "Klasse: ..." im letzten Beispiel ist interessant und fehlt in den anderen Beispielen. Es erstellt die erforderliche Freund-Deklaration - ohne einen neuen Klassennamen einzuführen.
Hans Olsson
Ein großer Unterschied zwischen dieser und der Antwort vom 19. November 2010 besteht darin, dass der Getter oder Setter von Fall zu Fall außer Kraft gesetzt werden kann. Auf diese Weise könnte man die Eingabe auf Reichweite überprüfen oder eine Änderungsbenachrichtigung senden, um Ereignis-Listener zu ändern, oder einen Ort zum Aufhängen eines Haltepunkts.
Eljay
28

In der C ++ - Sprache gibt es nichts, was auf allen Plattformen und Compilern funktioniert.

Aber wenn Sie bereit sind , plattformübergreifende Kompatibilität zu brechen und zu einem bestimmten Compiler begehen Sie in der Lage sein eine solche Syntax zu verwenden, zum Beispiel in Microsoft Visual C ++ können Sie tun

// declspec_property.cpp  
struct S {  
   int i;  
   void putprop(int j) {   
      i = j;  
   }  

   int getprop() {  
      return i;  
   }  

   __declspec(property(get = getprop, put = putprop)) int the_prop;  
};  

int main() {  
   S s;  
   s.the_prop = 5;  
   return s.the_prop;  
}
CompuChip
quelle
2
Dies funktioniert auch mit clang
Passer Bis zum
18

Sie können mit einem Mitglied von engagierter Art und zwingenden Getter und Setter zu einem gewissen Grad emulieren operator(type)und operator=für sie. Ob es eine gute Idee ist, ist eine andere Frage und ich gehe zu +1Kerrek SBs Antwort, um meine Meinung dazu zu äußern :)

Michael Krelin - Hacker
quelle
Sie können das Aufrufen einer Methode beim Zuweisen oder Ablesen nach einem solchen Typ emulieren, aber Sie können nicht unterscheiden, wer die Zuweisungsoperation aufruft (um sie zu verbieten, wenn es nicht der Feldeigentümer ist) - was ich versuche, indem Sie einen anderen Zugriff angeben Level für Getter und Setter.
Radim Vansa
@Flavius: Füge einfach ein friendzum Feldbesitzer hinzu.
Kennytm
17

Vielleicht werfen Sie einen Blick auf die Eigenschaftsklasse, die ich in den letzten Stunden zusammengestellt habe: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Sie können Eigenschaften haben, die sich wie folgt verhalten:

CTestClass myClass = CTestClass();

myClass.AspectRatio = 1.4;
myClass.Left = 20;
myClass.Right = 80;
myClass.AspectRatio = myClass.AspectRatio * (myClass.Right - myClass.Left);
Der Heilige
quelle
Schön, obwohl dies benutzerdefinierte Accessoren ermöglicht, gibt es nicht die Funktion für öffentliche Getter / private Setter, nach der ich gesucht habe.
Radim Vansa
17

Mit C ++ 11 können Sie eine Eigenschaftsklassenvorlage definieren und wie folgt verwenden:

class Test{
public:
  Property<int, Test> Number{this,&Test::setNumber,&Test::getNumber};

private:
  int itsNumber;

  void setNumber(int theNumber)
    { itsNumber = theNumber; }

  int getNumber() const
    { return itsNumber; }
};

Und hier ist die Eigenschaftsklassenvorlage.

template<typename T, typename C>
class Property{
public:
  using SetterType = void (C::*)(T);
  using GetterType = T (C::*)() const;

  Property(C* theObject, SetterType theSetter, GetterType theGetter)
   :itsObject(theObject),
    itsSetter(theSetter),
    itsGetter(theGetter)
    { }

  operator T() const
    { return (itsObject->*itsGetter)(); }

  C& operator = (T theValue) {
    (itsObject->*itsSetter)(theValue);
    return *itsObject;
  }

private:
  C* const itsObject;
  SetterType const itsSetter;
  GetterType const itsGetter;
};
Netter Mann
quelle
2
was tut C::*bedeuten? Ich habe so etwas noch nie gesehen?
Rika
1
Es ist ein Zeiger auf eine nicht statische Elementfunktion in der Klasse C. Dies ähnelt einem einfachen Funktionszeiger, aber um die Elementfunktion aufzurufen, müssen Sie ein Objekt angeben, für das die Funktion aufgerufen wird. Dies wird mit der Linie itsObject->*itsSetter(theValue)im obigen Beispiel erreicht. Sehen Sie hier für eine detailliertere Beschreibung dieser Funktion.
Christoph Böhme
@Niceman, gibt es ein Anwendungsbeispiel? Es scheint sehr kostspielig zu sein, Mitglied zu sein. Es ist auch als statisches Mitglied nicht besonders nützlich.
Grim Fandango
16

Wie viele andere bereits gesagt haben, gibt es keine integrierte Unterstützung in der Sprache. Wenn Sie jedoch auf den Microsoft C ++ - Compiler abzielen, können Sie die Microsoft-spezifische Erweiterung für Eigenschaften nutzen, die hier dokumentiert ist.

Dies ist das Beispiel von der verlinkten Seite:

// declspec_property.cpp
struct S {
   int i;
   void putprop(int j) { 
      i = j;
   }

   int getprop() {
      return i;
   }

   __declspec(property(get = getprop, put = putprop)) int the_prop;
};

int main() {
   S s;
   s.the_prop = 5;
   return s.the_prop;
}
Trasplazio Garzuglio
quelle
12

Nein, C ++ hat kein Konzept von Eigenschaften. Obwohl es umständlich sein kann, getThis () oder setThat (value) zu definieren und aufzurufen, geben Sie dem Verbraucher dieser Methoden eine Erklärung ab, dass einige Funktionen auftreten können. Der Zugriff auf Felder in C ++ teilt dem Verbraucher hingegen mit, dass keine zusätzlichen oder unerwarteten Funktionen auftreten. Eigenschaften würden dies weniger offensichtlich machen, da der Zugriff auf Eigenschaften auf den ersten Blick wie ein Feld zu reagieren scheint, aber tatsächlich wie eine Methode reagiert.

Nebenbei habe ich in einer .NET-Anwendung (einem sehr bekannten CMS) versucht, ein Kundenmitgliedschaftssystem zu erstellen. Aufgrund der Art und Weise, wie sie Eigenschaften für ihre Benutzerobjekte verwendet hatten, wurden Aktionen ausgelöst, die ich nicht erwartet hatte, und meine Implementierungen wurden auf bizarre Weise ausgeführt, einschließlich unendlicher Rekursion. Dies lag daran, dass ihre Benutzerobjekte beim Versuch, auf einfache Dinge wie StreetAddress zuzugreifen, die Datenzugriffsschicht oder ein globales Caching-System aufriefen. Ihr gesamtes System basierte auf dem, was ich als Missbrauch von Immobilien bezeichnen würde. Hätten sie Methoden anstelle von Eigenschaften verwendet, hätte ich wahrscheinlich viel schneller herausgefunden, was schief gelaufen ist. Hätten sie Felder verwendet (oder zumindest ihre Eigenschaften eher wie Felder verhalten lassen), wäre das System meiner Meinung nach einfacher zu erweitern und zu warten gewesen.

[Bearbeiten] Meine Gedanken geändert. Ich hatte einen schlechten Tag gehabt und war ein bisschen geschimpft. Diese Bereinigung sollte professioneller sein.

David Peterson
quelle
11

Basierend auf https://stackoverflow.com/a/23109533/404734 gibt es hier eine Version mit einem öffentlichen Getter und einem privaten Setter:

struct Foo
{
    class
    {
            int value;
            int& operator= (const int& i) { return value = i; }
            friend struct Foo;
        public:
            operator int() const { return value; }
    } alpha;
};
Kaiserludi
quelle
4

Dies ist nicht gerade eine Eigenschaft, aber es macht das, was Sie wollen, auf einfache Weise:

class Foo {
  int x;
public:
  const int& X;
  Foo() : X(x) {
    ...
  }
};

Hier verhält sich das große X wie public int X { get; private set; }in der C # -Syntax. Wenn Sie vollständige Eigenschaften wünschen, habe ich einen ersten Versuch unternommen, um sie hier zu implementieren .

Jan Turoň
quelle
2
Das ist keine gute Idee. Wenn Sie eine Kopie eines Objekts dieser Klasse erstellen, verweist die Referenz Xdes neuen Objekts weiterhin auf das Element des alten Objekts, da es nur wie ein Zeigerelement kopiert wird. Dies ist an sich schon schlecht, aber wenn das alte Objekt gelöscht wird, kommt es zusätzlich zu einer Speicherbeschädigung. Damit dies funktioniert, müssten Sie auch Ihren eigenen Kopierkonstruktor, Zuweisungsoperator und Verschiebungskonstruktor implementieren.
Toster
4

Sie wissen das wahrscheinlich, aber ich würde einfach Folgendes tun:

class Person {
public:
    std::string name() {
        return _name;
    }
    void name(std::string value) {
        _name = value;
    }
private:
    std::string _name;
};

Dieser Ansatz ist einfach, verwendet keine cleveren Tricks und erledigt den Job!

Das Problem ist jedoch, dass einige Leute ihren privaten Feldern keinen Unterstrich voranstellen möchten und diesen Ansatz daher nicht wirklich verwenden können, aber zum Glück für diejenigen, die dies tun, ist es wirklich einfach. :) :)

Die get- und set-Präfixe verleihen Ihrer API keine Klarheit, machen sie jedoch ausführlicher. Ich glaube nicht, dass sie nützliche Informationen hinzufügen, weil jemand, der eine API verwenden muss, wenn die API sinnvoll ist, wahrscheinlich erkennt, was sie ist verzichtet auf die Präfixe.

Eine weitere Sache, es ist leicht zu verstehen, dass dies Eigenschaften sind, weil namees kein Verb ist.

Im schlimmsten Fall muss die Person, wenn sie konsistent ist und die Person nicht erkannt hat, dass name()es sich um einen Accessor und name(value)einen Mutator handelt, diese nur einmal in der Dokumentation nachschlagen, um das Muster zu verstehen.

So sehr ich C # liebe, ich glaube nicht, dass C ++ überhaupt Eigenschaften braucht!

Eyal Solnik
quelle
Ihre Mutatoren sind sinnvoll, wenn Sie foo(bar)(anstelle der langsameren foo = bar) verwenden, aber Ihre Accessoren haben überhaupt nichts mit Eigenschaften zu tun ...
Matthias
@Matthias Die Aussage, dass es überhaupt nichts mit Eigenschaften zu tun hat, sagt mir nichts, können Sie näher darauf eingehen? Außerdem habe ich nicht versucht, sie zu vergleichen, aber wenn Sie einen Mutator und Accessor benötigen, können Sie diese Konvention verwenden.
Eyal Solnik
Die Frage betrifft das Softwarekonzept von Eigenschaften. Eigenschaften können so verwendet werden, als wären sie öffentliche Datenelemente (Verwendung), aber es handelt sich tatsächlich um spezielle Methoden, die als Accessoren (Deklaration) bezeichnet werden. Ihre Lösung hält sich an Methoden (gewöhnliche Getter / Setter) für die Deklaration und die Verwendung. Dies ist also erstens definitiv nicht die Verwendung, die das OP verlangt, sondern eine seltsame und unkonventionelle Namenskonvention (und damit auch zweitens kein syntaktischer Zucker).
Matthias
Als kleiner Nebeneffekt funktioniert Ihr Mutator überraschenderweise als Eigenschaft, da in C ++ am besten mit initialisiert wird, foo(bar)anstatt foo=barmit einer void foo(Bar bar)Mutator-Methode für eine _fooMitgliedsvariable erreicht zu werden.
Matthias
@Matthias Ich weiß, was Eigenschaften sind, ich schreibe seit über einem Jahrzehnt ziemlich viel C ++ und C #, ich streite nicht über den Nutzen von Eigenschaften und was sie sind, aber ich habe sie in C ++ nie WIRKLICH gebraucht, tatsächlich Sie Ich sage, dass sie als öffentliche Daten verwendet werden können, und das ist meistens richtig, aber es gibt Fälle in C #, in denen Sie Eigenschaften nicht einmal direkt verwenden können, z. B. das Übergeben einer Eigenschaft per Referenz, während Sie dies mit einem öffentlichen Feld tun können.
Eyal Solnik
4

Nein. Aber Sie sollten überlegen, ob es nur get: set-Funktion und keine zusätzliche Aufgabe gibt, die in get: set-Methoden ausgeführt wird. Machen Sie es einfach öffentlich.

Daniel Mor
quelle
2

Ich habe die Ideen aus mehreren C ++ - Quellen gesammelt und in ein schönes, immer noch recht einfaches Beispiel für Getter / Setter in C ++ eingefügt:

class Canvas { public:
    void resize() {
        cout << "resize to " << width << " " << height << endl;
    }

    Canvas(int w, int h) : width(*this), height(*this) {
        cout << "new canvas " << w << " " << h << endl;
        width.value = w;
        height.value = h;
    }

    class Width { public:
        Canvas& canvas;
        int value;
        Width(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } width;

    class Height { public:
        Canvas& canvas;
        int value;
        Height(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } height;
};

int main() {
    Canvas canvas(256, 256);
    canvas.width = 128;
    canvas.height = 64;
}

Ausgabe:

new canvas 256 256
resize to 128 256
resize to 128 64

Sie können es hier online testen: http://codepad.org/zosxqjTX

lama12345
quelle
Es gibt einen Speicheraufwand für die Aufbewahrung von Selbstauffrischungen + eine akward ctor-Syntax.
Red.Wave
Immobilien vorschlagen? Ich denke, es gab eine Reihe abgelehnter solcher Vorschläge.
Red.Wave
@ Red.Wave Verbeuge dich dann vor dem Lord und Meister der Ablehnung. Willkommen bei C ++. Clang und MSVC haben benutzerdefinierte Erweiterungen für Eigenschaften, wenn Sie die Selbstreferenzen nicht möchten.
Lama12345
Ich kann mich nie verbeugen. Ich denke, nicht jede Funktion ist angemessen. Für mich sind Objekte weit mehr als ein Paar Setter + Getter-Funktionen. Ich habe eigene Implementierungen versucht, um den unnötigen permanenten Speicheraufwand zu vermeiden, aber die Syntax und die Arbeit beim Deklarieren von Instanzen waren nicht zufriedenstellend. Ich habe mich für deklarative Makros interessiert, aber ich bin sowieso kein großer Fan von Makros. Und mein Ansatz führte schließlich zu Eigenschaften, auf die mit Funktionssyntax zugegriffen wurde, was viele, einschließlich mir, nicht gutheißen.
Red.Wave
0

Muss Ihre Klasse wirklich eine Invariante erzwingen oder handelt es sich nur um eine logische Gruppierung von Elementelementen? Wenn es das letztere ist, sollten Sie erwägen, das Ding zu einer Struktur zu machen und direkt auf die Mitglieder zuzugreifen.

emsr
quelle
0

Es gibt eine Reihe von Makros geschrieben Hier . Dies hat praktische Eigenschaftsdeklarationen für Werttypen, Referenztypen, schreibgeschützte Typen, starke und schwache Typen.

class MyClass {

 // Use assign for value types.
 NTPropertyAssign(int, StudentId)

 public:
 ...

}
tejusadiga2004
quelle