Est-ce une bonne pratique d'avoir un constructeur de classe qui utilise des paramètres par défaut ou devrais-je utiliser des constructeurs surchargés séparés? Par exemple:
// Use this...
class foo
{
private:
std::string name_;
unsigned int age_;
public:
foo(const std::string& name = "", const unsigned int age = 0) :
name_(name),
age_(age)
{
...
}
};
// Or this?
class foo
{
private:
std::string name_;
unsigned int age_;
public:
foo() :
name_(""),
age_(0)
{
}
foo(const std::string& name, const unsigned int age) :
name_(name),
age_(age)
{
...
}
};
Soit la version semble fonctionner, par exemple:
foo f1;
foo f2("Name", 30);
Quel style préférez-vous ou recommandez-vous et pourquoi?
Certainement une question de style. Je préfère les constructeurs avec des paramètres par défaut, tant que les paramètres ont un sens. Les classes du standard les utilisent également, ce qui leur est favorable.
Une chose à surveiller est que si vous avez des valeurs par défaut pour tous les paramètres sauf un, votre classe peut être convertie implicitement à partir de ce type de paramètre. Départ ce fil pour plus d'informations.
Je choisirais les arguments par défaut, d’autant plus que C++ ne vous permet pas de chaîner les constructeurs (vous devez donc dupliquer la liste des initialiseurs, voire davantage, pour chaque surcharge).
Cela dit, il y a quelques pièges avec des arguments par défaut, notamment le fait que des constantes peuvent être en ligne (et ainsi faire partie de l'interface binaire de votre classe). Une autre chose à surveiller est que l'ajout d'arguments par défaut peut transformer un constructeur explicite à plusieurs arguments en un constructeur implicite à un argument:
class Vehicle {
public:
Vehicle(int wheels, std::string name = "Mini");
};
Vehicle x = 5; // this compiles just fine... did you really want it to?
Cette discussion s'applique à la fois aux constructeurs, mais aussi aux méthodes et aux fonctions.
La bonne chose est que vous n'aurez pas besoin de surcharger les constructeurs/méthodes/fonctions pour chaque cas:
// Header
void doSomething(int i = 25) ;
// Source
void doSomething(int i)
{
// Do something with i
}
Le problème est que vous devez déclarer votre valeur par défaut dans l'en-tête, ce qui crée une dépendance masquée: comme lorsque vous modifiez le code d'une fonction intégrée, si vous modifiez la valeur par défaut dans votre en-tête, vous devez recompiler toutes les sources. en utilisant cet en-tête pour être sûr qu'ils utiliseront la nouvelle valeur par défaut.
Si vous ne le faites pas, les sources utiliseront toujours l'ancienne valeur par défaut.
La bonne chose est que si vos fonctions ne sont pas en ligne, vous contrôlez ensuite la valeur par défaut dans le source en choisissant le comportement d'une fonction. Par exemple:
// Header
void doSomething() ;
void doSomething(int i) ;
// Source
void doSomething()
{
doSomething(25) ;
}
void doSomething(int i)
{
// Do something with i
}
Le problème est que vous devez gérer plusieurs constructeurs/méthodes/fonctions et leurs redirections.
D'après mon expérience, les paramètres par défaut semblent cool à ce moment-là et rendent mon facteur de paresse heureux, mais ensuite, j'utilise la classe et je suis surpris lorsque la valeur par défaut entre en jeu. Je ne pense donc pas que ce soit une bonne idée. ; Mieux vaut avoir un className :: className () puis un className :: init (liste). Juste pour cette maintenabilité Edge.
Sam's answer donne la raison pour laquelle les arguments par défaut sont préférables pour les constructeurs plutôt que les surcharges. Je veux juste ajouter que C++ - 0x autorisera délégation d'un constructeur à un autre, supprimant ainsi le besoin de valeurs par défaut.
Les deux approches fonctionnent. Mais si vous avez une longue liste de paramètres facultatifs, créez un constructeur par défaut, puis demandez à votre fonction set de renvoyer une référence à cela. Puis enchaînez les setters.
class Thingy2
{
public:
enum Color{red,gree,blue};
Thingy2();
Thingy2 & color(Color);
Color color()const;
Thingy2 & length(double);
double length()const;
Thingy2 & width(double);
double width()const;
Thingy2 & height(double);
double height()const;
Thingy2 & rotationX(double);
double rotationX()const;
Thingy2 & rotatationY(double);
double rotatationY()const;
Thingy2 & rotationZ(double);
double rotationZ()const;
}
main()
{
// gets default rotations
Thingy2 * foo=new Thingy2().color(ret)
.length(1).width(4).height(9)
// gets default color and sizes
Thingy2 * bar=new Thingy2()
.rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
// everything specified.
Thingy2 * thing=new Thingy2().color(ret)
.length(1).width(4).height(9)
.rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
}
Maintenant, lors de la construction des objets, vous pouvez choisir et choisir les propriétés à remplacer et celles que vous avez définies sont explicitement nommées. Beaucoup plus lisible :)
De plus, vous ne devez plus vous souvenir de l'ordre des arguments du constructeur.
Si créer des constructeurs avec des arguments est mauvais (comme beaucoup le diraient), leur fabrication avec des arguments par défaut est encore pire. J'ai récemment commencé à penser que les arguments de ctor sont mauvais, car votre logique de ctor devrait être aussi minime que possible . Comment gérez-vous la gestion des erreurs dans le ctor, est-ce que quelqu'un devrait passer un argument qui n'a aucun sens? Vous pouvez soit lancer une exception, ce qui est une mauvaise nouvelle si tous vos appelants ne sont pas prêts à envelopper tous les "nouveaux" appels dans des blocs try, ou en définissant une variable membre "is-initialized", qui est en quelque sorte un bidouillage.
Par conséquent, le seul moyen de vous assurer que les arguments transmis lors de la phase d'initialisation de votre objet consiste à configurer une méthode distincte initialize () dans laquelle vous pouvez vérifier le code de retour.
L'utilisation d'arguments par défaut est mauvaise pour deux raisons. Tout d'abord, si vous souhaitez ajouter un autre argument au ctor, vous devez le placer au début et modifier l'ensemble de l'API. De plus, la plupart des programmeurs sont habitués à concevoir une API de la manière dont elle est utilisée dans la pratique - c'est surtout vrai pour les API non publiques utilisées à l'intérieur d'une organisation où la documentation formelle peut ne pas exister. Lorsque les autres programmeurs s'aperçoivent que la majorité des appels ne contiennent aucun argument, ils en feront de même, en restant parfaitement inconscients du comportement par défaut que vos arguments par défaut leur imposent.
En outre, il convient de noter que le guide de style de Google C++ évite les deux arguments ctor (sauf si absolument nécessaire), et arguments par défaut des fonctions ou des méthodes .
Une autre chose à considérer est de savoir si la classe peut ou non être utilisée dans un tableau:
foo bar[400];
Dans ce scénario, l'utilisation du paramètre par défaut ne présente aucun avantage.
Cela ne fonctionnerait certainement pas:
foo bar("david", 34)[400]; // NOPE
J'irais avec les paramètres par défaut, pour cette raison: Votre exemple suppose que les paramètres ctor correspondent directement aux variables membres. Mais que se passe-t-il si ce n'est pas le cas et que vous devez traiter les paramètres avant l'initialisation de l'objet? Avoir un acteur commun serait la meilleure solution.
Choix principalement personnel. Cependant, la surcharge peut faire tout ce que les paramètres par défaut peuvent faire, mais pas vice-versa.
Exemple:
Vous pouvez utiliser la surcharge pour écrire A (int x, toto & a) et A (int x), mais vous ne pouvez pas utiliser le paramètre par défaut pour écrire A (int x, toto & = null).
La règle générale est d'utiliser tout ce qui a du sens et rend le code plus lisible.
Une chose qui me dérange avec les paramètres par défaut est que vous ne pouvez pas spécifier les derniers paramètres mais utiliser les valeurs par défaut pour les premiers. Par exemple, dans votre code, vous ne pouvez pas créer un Foo sans nom mais avec un âge donné (cependant, si je me souviens bien, cela sera possible en C++ 0x, avec la syntaxe de construction unifiée). Parfois, cela a du sens, mais cela peut aussi être vraiment gênant.
À mon avis, il n'y a pas de règle générale. Personnellement, j'ai tendance à utiliser plusieurs constructeurs (ou méthodes) surchargés, sauf si seul le dernier argument nécessite une valeur par défaut.
Question de style, mais comme Matt l'a dit, envisagez définitivement de marquer les constructeurs avec des arguments par défaut qui autoriseraient la conversion implicite comme "explicite" afin d'éviter une conversion automatique involontaire. Ce n'est pas une exigence (et cela peut ne pas être préférable si vous créez une classe wrapper vers laquelle vous voulez convertir implicitement), mais cela peut éviter des erreurs.
Personnellement, j'aime bien les valeurs par défaut lorsque cela convient, parce que je n'aime pas le code répété. YMMV.