C ++ 11 a-t-il des propriétés de style C #?


93

En C #, il existe un bon sucre de syntaxe pour les champs avec getter et setter. De plus, j'aime les propriétés auto-implémentées qui me permettent d'écrire

public Foo foo { get; private set; }

En C ++, je dois écrire

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

Y a-t-il un tel concept dans le C ++ 11 qui me permet d'avoir du sucre de syntaxe à ce sujet?


62
Cela pourrait être fait avec quelques macros. s'enfuit dans la honte
Mankarse

7
@Eloff: Rendre tout ce qui est public est TOUJOURS une mauvaise idée.
Kaiserludi

8
Un tel concept n'existe pas! Et vous n'en avez pas besoin aussi: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
a) cette question est assez ancienne b) je demandais du sucre de syntaxe, cela me permettrait de me débarrasser des parenthèses c) bien que l'article présente des arguments valables contre l'adaptation des propriétés, que C ++ 'ait ou non besoin' de propriétés est très subjectif. C ++ est l'équivalent de Touring-machine même sans eux, mais cela ne signifie pas qu'avoir un tel sucre de syntaxe rendrait C ++ plus productif.
Radim Vansa

3
Définitivement pas.

Réponses:


87

En C ++, vous pouvez écrire vos propres fonctionnalités. Voici un exemple d'implémentation de propriétés utilisant des classes sans nom. Article Wikipédia

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;
};

Vous pouvez écrire vos propres getters et setters en place et si vous voulez un accès aux membres de la classe de détenteurs, vous pouvez étendre cet exemple de code.


1
Des idées sur la façon de modifier ce code pour avoir toujours une variable de membre privé à laquelle Foo peut accéder en interne, tandis que l'API publique n'expose que la propriété? Je pourrais bien sûr simplement faire de Foo un ami de alpha / beta, mais alors je devrais quand même écrire alpha.value, pour accéder à la valeur, mais je préférerais que l'accès direct à la variable membre depuis l'intérieur de Foo ressemble plus à accéder à un membre de Foo lui-même et non à un membre d'une classe de propriété imbriquée spéciale.
Kaiserludi

1
@Kaiserludi Oui: dans ce cas, rendez alpha et bravo privés. Dans Foo, vous pouvez lire / écrire avec ces "propriétés" ci-dessus, mais en dehors de Foo, ce ne serait plus possible. Pour contourner cela, créez une référence const qui est publique. Il est accessible de l'extérieur, mais uniquement pour la lecture puisqu'il s'agit d'une référence constante. La seule mise en garde est que vous aurez besoin d'un autre nom pour la référence publique const. J'utiliserais personnellement _alphapour la variable privée et alphapour la référence.
Kapichu

1
@Kapichu: pas une solution, pour 2 raisons. 1) En C #, les getters / setters de propriété sont souvent utilisés pour intégrer des contrôles de sécurité qui sont forcés aux utilisateurs publics de la classe tout en permettant aux fonctions membres d'accéder directement à la valeur. 2) les références const ne sont pas libres: selon le compilateur / la plate-forme, elles seront agrandies sizeof(Foo).
ceztko

@psx: en raison des limites de cette approche, je l'éviterais et j'attendrais un ajout approprié à la norme, si elle apparaît un jour.
ceztko

@Kapichu: Mais alpha et bravo dans l'exemple de code sont les propriétés. Je voudrais accéder directement à la variable elle-même depuis l'intérieur de l'implémentation de Foo, sans avoir besoin d'utiliser une propriété, alors que je voudrais uniquement exposer l'accès via une propriété dans l'API.
Kaiserludi

53

C ++ n'a pas cela intégré, vous pouvez définir un modèle pour imiter la fonctionnalité des propriétés:

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;
};

Pour définir une propriété :

Property<float> x;

Pour implémenter un getter / setter personnalisé, héritez simplement:

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;

Pour définir une propriété en lecture seule :

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

Et pour l' utiliser en classeOwner :

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

Vous pouvez définir certains des éléments ci-dessus dans des macros pour les rendre plus concis.


Je suis curieux de savoir si cela se compile en une fonctionnalité à coût nul, je ne sais pas si l'encapsulation de chaque membre de données dans une instance de classe entraînera le même type de compression de structure, par exemple.
Dai

1
La logique "getter / setter personnalisé" pourrait être rendue syntaxiquement plus claire par l'utilisation de fonctions lambda, malheureusement vous ne pouvez pas définir un lambda en dehors d'un contexte exécutable en C ++ (encore!) Donc sans utiliser une macro de préprocesseur vous vous retrouvez avec du code qui est juste aussi compliqué que les getters / setters stupides, ce qui est malheureux.
Dai

2
La "classe: ..." dans le dernier exemple est intéressante et absente des autres exemples. Il crée la déclaration d'ami nécessaire - sans introduire de nouveau nom de classe.
Hans Olsson

Une grande différence entre cela et la réponse du 19 novembre 2010 est que cela permet de remplacer le getter ou le setter au cas par cas. De cette façon, on peut vérifier que l'entrée est à portée, ou envoyer une notification de modification pour changer les écouteurs d'événements, ou un endroit pour accrocher un point d'arrêt.
Eljay

Notez que le virtualn'est probablement pas nécessaire pour la plupart des cas d'utilisation, car il est peu probable qu'une propriété soit utilisée de manière polymorphe.
Eljay

28

Il n'y a rien dans le langage C ++ qui fonctionnera sur toutes les plates-formes et tous les compilateurs.

Mais si vous êtes prêt à rompre la compatibilité multiplateforme et à vous engager dans un compilateur spécifique, vous pourrez peut-être utiliser une telle syntaxe, par exemple dans Microsoft Visual C ++, vous pouvez le faire

// 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;  
}


18

Vous pouvez émuler getter et setter dans une certaine mesure en ayant un membre de type dédié et en remplaçant operator(type)et operator=pour lui. Que ce soit une bonne idée est une autre question et je vais à +1la réponse de Kerrek SB pour exprimer mon opinion là-dessus :)


Vous pouvez émuler l'appel d'une méthode lors de l'affectation ou de la lecture à partir d'un tel type, mais vous ne pouvez pas distinguer qui appelle l'opération d'assignation (pour l'interdire si ce n'est pas le propriétaire du champ) - ce que j'essaie de faire en spécifiant un accès différent niveau pour getter et setter.
Radim Vansa

@Flavius: Ajoutez simplement un friendau propriétaire du champ.
kennytm

17

Jetez peut-être un œil à la classe de propriété que j'ai assemblée au cours des dernières heures: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Cela vous permet d'avoir des propriétés qui se comportent comme ceci:

CTestClass myClass = CTestClass();

myClass.AspectRatio = 1.4;
myClass.Left = 20;
myClass.Right = 80;
myClass.AspectRatio = myClass.AspectRatio * (myClass.Right - myClass.Left);

Bien, cependant, bien que cela autorise les accesseurs définis par l'utilisateur, il n'y a pas la fonctionnalité getter public / setter privé que je recherchais.
Radim Vansa

17

Avec C ++ 11, vous pouvez définir un modèle de classe Property et l'utiliser comme ceci:

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; }
};

Et voici le modèle de classe Property.

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;
};

2
que veut C::*dire? Je n'ai jamais rien vu de tel auparavant?
Rika

1
C'est un pointeur vers une fonction membre non statique de la classe C. Ceci est similaire à un pointeur de fonction simple, mais pour appeler la fonction membre, vous devez fournir un objet sur lequel la fonction est appelée. Ceci est réalisé avec la ligne itsObject->*itsSetter(theValue)dans l'exemple ci-dessus. Voir ici pour une description plus détaillée de cette fonctionnalité.
Christoph Böhme

@Niceman, y a-t-il un exemple d'utilisation? Cela semble très coûteux à avoir comme membre. Ce n'est pas particulièrement utile en tant que membre statique non plus.
Grim Fandango

16

Comme beaucoup d'autres l'ont déjà dit, il n'y a pas de support intégré dans la langue. Toutefois, si vous ciblez le compilateur Microsoft C ++, vous pouvez profiter de l'extension spécifique à Microsoft pour les propriétés qui est documentée ici.

Voici l'exemple de la page liée:

// 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;
}

12

Non, C ++ n'a pas de concept de propriétés. Bien qu'il puisse être difficile de définir et d'appeler getThis () ou setThat (valeur), vous faites une déclaration au consommateur de ces méthodes que certaines fonctionnalités peuvent se produire. L'accès aux champs en C ++, en revanche, indique au consommateur qu'aucune fonctionnalité supplémentaire ou inattendue ne se produira. Les propriétés rendraient cela moins évident car l'accès aux propriétés semble à première vue réagir comme un champ, mais réagit en fait comme une méthode.

En passant, je travaillais dans une application .NET (un CMS très connu) essayant de créer un système d'adhésion client. En raison de la manière dont ils utilisaient les propriétés de leurs objets utilisateur, des actions se déclenchaient que je n'avais pas anticipées, provoquant l'exécution de mes implémentations de manière bizarre, y compris une récursivité infinie. Cela était dû au fait que leurs objets utilisateur appelaient la couche d'accès aux données ou un système de mise en cache global lorsqu'ils tentaient d'accéder à des choses simples comme StreetAddress. Tout leur système était fondé sur ce que j'appellerais un abus de propriété. S'ils avaient utilisé des méthodes plutôt que des propriétés, je pense que j'aurais compris ce qui n'allait pas beaucoup plus rapidement. S'ils avaient utilisé des champs (ou du moins fait en sorte que leurs propriétés se comportent plus comme des champs), je pense que le système aurait été plus facile à étendre et à maintenir.

[Edit] J'ai changé d'avis. J'avais eu une mauvaise journée et j'ai fait un peu de diatribe. Ce nettoyage devrait être plus professionnel.



4

Ce n'est pas exactement une propriété, mais il fait ce que vous voulez de manière simple:

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

Ici, le grand X se comporte comme public int X { get; private set; }dans la syntaxe C #. Si vous voulez des propriétés à part entière, j'ai fait un premier plan pour les implémenter ici .


2
Ce n'est pas une bonne idée. Chaque fois que vous faites une copie d'un objet de cette classe, la référence Xdu nouvel objet pointera toujours vers le membre de l'ancien objet, car il est simplement copié comme un membre pointeur. C'est mauvais en soi, mais lorsque l'ancien objet est supprimé, vous obtenez une corruption de mémoire en plus. Pour que cela fonctionne, vous devez également implémenter votre propre constructeur de copie, opérateur d'affectation et constructeur de déplacement.
toster

4

Vous le savez probablement, mais je ferais simplement ce qui suit:

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

Cette approche est simple, n'utilise aucune astuce intelligente et elle fait le travail!

Le problème est cependant que certaines personnes n'aiment pas préfixer leurs champs privés avec un trait de soulignement et ne peuvent donc pas vraiment utiliser cette approche, mais heureusement pour ceux qui le font, c'est vraiment simple. :)

Les préfixes get and set n'ajoutent pas de clarté à votre API mais les rendent plus verbeux et la raison pour laquelle je ne pense pas qu'ils ajoutent des informations utiles est que lorsque quelqu'un a besoin d'utiliser une API si l'API a du sens, elle réalisera probablement de quoi il s'agit se passe des préfixes.

Encore une chose, il est facile de comprendre que ce sont des propriétés car name n'est pas un verbe.

Dans le pire des cas, si les API sont cohérentes et que la personne n'a pas réalisé qu'il name()s'agit d'un accesseur etname(value) est un mutateur, elle n'aura qu'à la rechercher une fois dans la documentation pour comprendre le modèle.

Autant j'aime C #, je ne pense pas que C ++ ait besoin de propriétés du tout!


Vos mutateurs ont du sens si l'on utilise foo(bar)(au lieu du plus lent foo = bar), mais vos accesseurs n'ont rien à voir avec les propriétés du tout ...
Matthias

@Matthias En disant que cela n'a rien à voir avec les propriétés, cela ne me dit rien, pouvez-vous élaborer? en plus je n'ai pas essayé de les comparer mais si vous avez besoin d'un mutateur et d'un accesseur vous pouvez utiliser cette convention.
Eyal Solnik

La question concerne le concept logiciel des propriétés. Les propriétés peuvent être utilisées comme s'il s'agissait de membres de données publiques (utilisation), mais ce sont en fait des méthodes spéciales appelées accesseurs (déclaration). Votre solution s'en tient aux méthodes (getters / setters ordinaires) pour la déclaration et l'utilisation. Donc, tout d'abord, ce n'est certainement pas l'usage que l'OP demande, mais plutôt une convention de dénomination étrange et non conventionnelle (et donc, deuxièmement, pas de sucre syntaxique).
Matthias

En tant qu'effet secondaire mineur, votre mutateur fonctionne étonnamment comme une propriété, car en C ++, il est préférable d'initialiser avec foo(bar)au lieu de cela, foo=barce qui peut être obtenu avec une void foo(Bar bar)méthode mutator sur une _foovariable membre.
Matthias

@Matthias Je sais ce que sont les propriétés, j'écris un peu C ++ et C # depuis plus d'une décennie, je ne discute pas de l'avantage des propriétés et de ce qu'elles sont mais je n'en ai jamais vraiment eu besoin en C ++, en fait vous disons qu'ils peuvent être utilisés comme données publiques et c'est généralement vrai, mais il y a des cas en C # où vous ne pouvez même pas utiliser les propriétés directement, comme passer une propriété par ref alors qu'avec un champ public, vous le pouvez.
Eyal Solnik

4

Non .. Mais vous devriez considérer si c'est juste la fonction get: set et aucune tâche supplémentaire préformée dans les méthodes get: set ne la rendent publique.


2

J'ai rassemblé les idées de plusieurs sources C ++ et les ai mises dans un bel exemple encore assez simple pour les getters / setters en C ++:

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;
}

Production:

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

Vous pouvez le tester en ligne ici: http://codepad.org/zosxqjTX


Il y a une surcharge de mémoire pour garder les auto-réfrences, + une syntaxe de ctor akward.
Red.Wave

Proposer des biens? Je suppose qu'il y a eu un tas de propositions rejetées.
Red.Wave

@ Red.Wave Bow vers le seigneur et maître du rejet alors. Bienvenue dans C ++. Clang et MSVC ont des extensions personnalisées pour les propriétés, si vous ne voulez pas les auto-références.
lama12345

Je ne peux jamais m'incliner. Toutes les fonctionnalités ne sont même pas appropriées, je pense. Pour moi, les objets sont bien plus qu'une paire de fonctions setter + getter. J'ai essayé mes propres implémentations en évitant la surcharge de mémoire permanente inutile, mais la syntaxe et la corvée de déclaration des instances n'étaient pas satisfaisantes; J'étais attiré par les macros déclaratives, mais je ne suis pas un grand fan des macros de toute façon. Et mon approche a finalement conduit à des propriétés accessibles avec une syntaxe de fonction, que beaucoup, moi-même, n'approuvent pas.
Red.Wave

0

Votre classe a-t-elle vraiment besoin d'appliquer un invariant ou s'agit-il simplement d'un groupement logique d'éléments membres? Si c'est ce dernier, vous devriez envisager de faire de l'objet une structure et d'accéder directement aux membres.


0

Il existe un ensemble de macros écrites ici . Cela a des déclarations de propriétés pratiques pour les types valeur, les types référence, les types en lecture seule, les types forts et faibles.

class MyClass {

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

 public:
 ...

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.