Je viens du monde de C # et j'apprends le C++. Je me demandais comment obtenir et définir des fonctions en C++. Leur utilisation en C # est très populaire et des outils tels que Visual Studio en font la promotion en les rendant très faciles et rapides à implémenter. Cependant, cela ne semble pas être le cas dans le monde C++.
Voici le code C # 2.0:
public class Foo
{
private string bar;
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
Ou, en C # 3.0:
public class Foo { get; set; }
Les gens peuvent dire, bien quel est le point là-dedans? Pourquoi ne pas simplement créer un champ public et en faire une propriété ultérieurement si vous en avez besoin; Honnêtement, je ne suis pas sûr. Je viens de le faire par bonne pratique parce que je l'ai vu faire tellement de fois.
Maintenant que je suis tellement habitué à le faire, j’ai le sentiment que je devrais conserver cette habitude dans mon code C++, mais est-ce vraiment nécessaire? Je ne le vois pas aussi souvent qu'avec C #.
Quoi qu'il en soit, voici le C++ de ce que je comprends:
class Foo
{
public:
std::string GetBar() const; // Thanks for the tip Earwicker.
void SetBar(std::string bar);
private:
std::string bar;
}
const std::string Foo::GetBar()
{
return bar;
}
void Foo::SetBar(std::string bar)
{
// Also, I always wonder if using 'this->' is good practice.
this->bar = bar;
}
Pour moi, cela ressemble à beaucoup de travail pour les jambes; Si vous envisagez d'utiliser les outils de Visual Studio, l'implémentation de C # prend littéralement quelques secondes et le C++ m'a pris beaucoup plus de temps à taper. Je pense que cela ne vaut pas la peine, en particulier lorsque l'alternative comporte 5 lignes:
class Foo
{
public:
std::string Bar;
}
D'après ce que je comprends, voici les avantages:
Et les inconvénients:
Pourquoi ai-je choisi la réponse avec moins de votes ? En fait, j'étais sur le point de choisir la réponse de veefu ; Cependant, mon opinion personnelle (qui est apparemment controversée) est que la réponse est trop longue pour le pudding.
La réponse que j'ai choisie, en revanche, semble argumenter les deux côtés; Je pense que les getters et les setters sont diaboliques s’ils sont utilisés de manière excessive (je veux dire par là, quand ce n’est pas nécessaire et que cela briserait le modèle commercial), mais pourquoi ne devrions-nous pas avoir une fonction appelée GetBalance()
?
Ce serait sûrement beaucoup plus polyvalent que PrintBalance()
; Et si je voulais le montrer à l'utilisateur autrement que par la classe? Maintenant, dans un certain sens, GetBalance()
n'est peut-être pas suffisamment pertinent pour affirmer que "les getters et les setters sont bons" car ils n'ont pas (ou peut-être, ne devrait pas) un accompagnateur, et en parlant de quoi, une fonction appelé SetBalance(float f)
pourrait être mauvais (à mon avis) car cela impliquerait pour le développeur de la fonction que le compte doit être manipulé en dehors de la classe, ce qui n'est pas une bonne chose.
Je dirais que fournir des accesseurs est plus important en C++ qu'en C #.
C++ n'a pas de support intégré pour les propriétés. En C #, vous pouvez remplacer un champ public par une propriété sans changer le code de l'utilisateur. En C++, c'est plus difficile .
Pour moins de dactylographie, vous pouvez implémenter des setters/getters triviaux en tant que méthodes inline:
class Foo
{
public:
const std::string& bar() const { return _bar; }
void bar(const std::string& bar) { _bar = bar; }
private:
std::string _bar;
};
Et n'oubliez pas que les getters et les setters sont un peu pervers.
Au risque d’être argumentative, je soutiens un point de vue opposé que j’avais rencontré pour la première fois en lisant "Holub on Patterns". C'était un point de vue qui était très difficile, mais qui m'a semblé logique:
Getters et Setters sont le mal
L'utilisation de getters et de setters s'oppose aux principes fondamentaux de la conception orientée objet: l'abstraction de données et l'encapsulation. Une utilisation excessive des getters et des setters rendra votre code moins agile et moins maintenable à long terme. Ils exposent finalement l'implémentation sous-jacente de votre classe, en verrouillant les détails d'implémentation dans l'interface de la classe.
Imaginez que votre champ 'std :: string Foo :: bar' doive passer d'une classe std :: string à une autre classe de chaîne, qui, par exemple, est mieux optimisée ou prend en charge un jeu de caractères différent. Vous devrez modifier le champ de données privé, le getter, le setter et tout le code client de cette classe qui appelle ces geters et ces setters.
Plutôt que de concevoir vos classes pour "fournir des données" et "recevoir des données", définissez-les pour "effectuer des opérations" ou "fournir des services". Demandez-vous pourquoi vous écrivez une fonction "GetBar". Que faites-vous avec ces données? Peut-être que vous affichez ces données ou effectuez un traitement dessus. Ce processus est-il mieux exposé comme méthode de Foo?
Cela ne veut pas dire que les getters et les setters n’ont pas leur but. En C #, je pense que la raison fondamentale de leur utilisation est de s’interfacer avec l’IDE de Visual Studio à interface graphique, mais si vous vous trouvez en train de les écrire en C++, il est probablement préférable de prendre du recul, de regarder votre conception et de voir si quelque chose se passe. est manquant.
Je vais essayer de simuler un exemple à illustrer.
// A class that represents a user's bank account
class Account {
private:
int balance_; // in cents, lets say
public:
const int& GetBalance() { return balance_; }
void SetBalance(int b) { balance_ = b; }
};
class Deposit {
private:
int ammount_;
public:
const int& GetAmount() { return ammount_; }
void SetAmmount(int a) { _balance = a; }
};
void DoStuffWithAccount () {
Account a;
// print account balance
int balance = a.GetBalance();
std::cout << balance;
// deposit some money into account
Deposit d(10000);
a.SetBalance( a.GetBalance() + d.GetValue());
}
Il ne faut pas longtemps pour voir que cela est très mal conçu.
Les getters et les setters compliquent la résolution des problèmes, car le code client DoStuffWithAccount est maintenant lié au type de données utilisé pour implémenter le solde du compte.
Alors passons ce code et voyons ce que nous pouvons améliorer
// A class that represents a user's bank account
class Account {
private:
float balance_;
public:
void Deposit(float b) { balance_ += b; }
void Withdraw(float w) { balance_ -= w; }
void DisplayDeposit(std::ostream &o) { o << balance_; }
};
void DoStuffWithAccount () {
Account a;
// print account balance
a.DisplayBalance(std::cout);
// deposit some money into account
float depositAmt = 1000.00;
a.Deposit(depositAmt);
a.DisplayBalance(std::cout);
}
Le «float» est un pas dans la bonne direction. Cela dit, vous auriez pu changer le type interne en "float" tout en prenant en charge l'idiome getter/setter:
class Account {
private:
// int balance_; // old implementation
float balance_;
public:
// support the old interface
const int& GetBalance() { return (int) balance_; }
void SetBalance(int b) { balance_ = b; }
// provide a new interface for the float type
const float& GetBalance() { return balance_; } // not legal! how to expose getter for float as well as int??
void SetBalance(float b) { balance_ = b; }
};
mais il ne faut pas longtemps pour comprendre que la configuration getter/setter double votre charge de travail et complique les choses, car vous devez prendre en charge à la fois le code qui utilise ints et le nouveau code qui utilise des éléments flottants. La fonction de dépôt facilite l’élargissement de la gamme de types de dépôts.
Une classe semblable à un compte n'est probablement pas le meilleur exemple, car "obtenir" le solde du compte est une opération naturelle pour un compte. L’essentiel, cependant, est que vous devez faire attention avec les Getters et les Setters. Ne prenez pas l'habitude d'écrire des getters et des setters pour chaque membre de données. Il est assez facile de vous exposer et de vous enfermer dans une implémentation si vous ne faites pas attention.
Dans votre exemple:
class Foo
{
public:
const std::string GetBar(); // Should this be const, not sure?
Vous voulez probablement dire ceci:
std::string GetBar() const;
Mettre la const
à la fin signifie "Cette fonction ne modifie pas l'instance de Foo sur laquelle elle est appelée", ce qui la marque comme un getter pur.
Les getters purs apparaissent fréquemment en C++. Un exemple dans std::ostringstream
est la fonction str()
. La bibliothèque Standard utilise souvent le même nom de fonction pour une paire de fonctions de lecture/définition, str
étant à nouveau un exemple.
Quant à savoir si c'est trop de travail à faire et si cela en vaut la peine, cela semble une question étrange! Si vous devez donner aux clients accès à certaines informations, fournissez un getter. Si vous ne le faites pas, alors ne le faites pas.
[modifier] Il semble que je dois souligner que les utilisateurs doivent valider des paramètres et appliquer des invariants, de sorte qu'ils ne sont généralement pas aussi simples qu'ici. [/modifier]
Pas avec tous, car pour la dactylographie supplémentaire. J'ai tendance à les utiliser beaucoup plus souvent maintenant que Visual Assist me donne le "champ encapsulé".
Les démarches ne sont pas plus complexes si vous implémentez uniquement les paramètres/getters par défaut intégrés dans la déclaration de classe (ce que j'ai tendance à faire; les paramètres plus complexes se déplacent toutefois vers le corps).
Quelques notes:
constness: Oui, le getter devrait être const. Il est inutile de rendre la valeur de retour const, cependant, si vous retournez par valeur. Pour les valeurs de retour potentiellement complexes, vous pouvez utiliser const & quo:
std::string const & GetBar() const { return bar; }
Setter chaining: De nombreux développeurs aiment modifier le setter comme tel:
Foo & SetBar(std::string const & bar) { this->bar = bar; return *this; }
Ce qui permet d’appeler plusieurs setters en tant que tels:
Foo foo;
foo.SetBar("Hello").SetBaz("world!");
Ce n'est pas universellement accepté comme une bonne chose, cependant.
__declspec (property): Visual C++ fournit cette extension non standard afin que les appelants puissent utiliser à nouveau la syntaxe de la propriété. Cela augmente un peu le travail dans la classe, mais rend le code de l'appelant beaucoup plus convivial.
Donc, en conclusion, il y a un peu de travail supplémentaire, mais quelques décisions à prendre en C++. Typique;)
Il n'y a pas de convention vraiment stricte à ce sujet, comme en C # ou en Java. De nombreux programmeurs C++ ne font que rendre publique la variable et se ménagent le problème.
Comme d'autres réponses l'ont dit, vous ne devriez pas souvent avoir besoin de méthodes établies et, dans une certaine mesure, de méthodes.
Mais si et quand vous les créez, il n’est pas nécessaire de taper plus que nécessaire:
class Foo
{
public:
std::string Bar() const { return bar; }
void Bar(const std::string& bar) { this->bar = bar; }
private:
std::string bar;
};
La déclaration des fonctions en ligne dans la classe enregistre la saisie et indique au compilateur que vous souhaitez les fonctions en ligne. Et ce n’est pas beaucoup plus typé que les équivalents C # ..__ Une chose à noter est que j’ai supprimé les préfixes get/set. Au lieu de cela, nous avons juste deux surcharges Bar (). C'est assez courant en C++ (après tout, s'il ne prend aucun argument, nous savons que c'est le getter, et s'il faut un argument, c'est le setter. Nous n'avons pas besoin du nom pour nous le dire enregistre un peu plus de frappe.
J'utilise rarement des getters et des setters dans mon propre code. La réponse de Veefu me semble bien.
Si vous insistez pour avoir des getters et/ou des setters, vous pouvez utiliser des macros pour réduire le plateau de la chaudière.
#define GETTER(T,member) const T& Get##member() const { return member; }
#define SETTER(T,member) void Set##member(const T & value) { member = value; }
class Foo
{
public:
GETTER(std::string, bar)
SETTER(std::string, bar)
private:
std::string bar;
}
Obtention et définition des membres de données en tant que membres de données: Bad .
Obtention et mise en place des éléments de l'abstraction: Bien .
Les arguments contre Get/Set en termes de conception d’API dans l’exemple bancaire sont clairs. N'exposez pas les champs ou les propriétés s'ils permettent aux utilisateurs d'enfreindre vos règles métier.
Cependant, une fois que vous avez décidé que vous avez besoin d'un champ ou d'une propriété, utilisez toujours une propriété.
Les propriétés automatiques de c # sont très faciles à utiliser et de nombreux scénarios (liaison de données, sérialisation, etc.) ne fonctionnent pas avec les champs, mais nécessitent des propriétés.
get and set est une douleur infligée aux gens si vous devez les utiliser dans n’importe quelle langue.
Eiffel a beaucoup mieux à faire là où tout ce qui diffère est la quantité d'informations que vous devez fournir pour obtenir la réponse - une fonction avec 0 paramètre est identique à l'accès à une variable membre, et vous pouvez changer librement entre elles.
Lorsque vous contrôlez les deux côtés d'une interface, la définition de l'interface ne semble pas être un gros problème. Toutefois, lorsque vous souhaitez modifier les détails de l'implémentation et que cela implique la recompilation du code client, comme c'est le cas habituel en C++, vous souhaitez pouvoir le minimiser autant que possible. En tant que tel, pImpl et get/set seraient davantage utilisés dans les API publiques afin d'éviter de tels dommages.
Les méthodes Get et Set sont utiles si vous avez des contraintes dans une valeur de variable. Par exemple, dans de nombreux modèles mathématiques, il existe une contrainte pour conserver une certaine variable float dans la plage [0,1]. Dans ce cas, Get et Set (spécialement Set) peuvent jouer un rôle intéressant:
class Foo{
public:
float bar() const { return _bar; }
void bar(const float& new_bar) { _bar = ((new_bar <= 1) && (new_bar >= 0))?new_bar:_bar; } // Keeps inside [0,1]
private:
float _bar; // must be in range [0,1]
};
De plus, certaines propriétés doivent être recalculées avant d'être lues. Dans ces cas-là, recalculer chaque cycle peut prendre beaucoup de temps de calcul inutile. Donc, une façon de l’optimiser est de ne recalculer que lors de la lecture. Pour ce faire, surchargez la méthode Get afin de mettre à jour la variable avant de la lire.
Sinon, s'il n'est pas nécessaire de valider les valeurs d'entrée ou de mettre à jour les valeurs de sortie, rendre la propriété publique n'est pas un crime et vous pouvez continuer.
Si vous développez des composants COM, alors oui, il est très populaire.
Si vous utilisez C++/CLI en tant que votre variante de C++, alors il a un support de propriété natif dans le langage, vous pouvez donc utiliser
property String^ Name;
C'est pareil que
String Name{get;set;}
en C #. Si vous avez besoin d’un contrôle plus précis sur les méthodes get/set, vous pouvez utiliser
property String^ Name
{
String^ get();
void set(String^ newName);
}
dans l'en-tête et
String^ ClassName::Name::get()
{
return m_name;
}
void ClassName::Name::set(String^ newName)
{
m_name = newName;
}
dans le fichier .cpp. Je ne me souviens plus très bien, mais je pense que vous pouvez avoir différentes autorisations d'accès pour les méthodes get et set (public/privé, etc.).
Colin