Question

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?

Était-ce utile?

La solution

Certainement une question de style. Je préfère les constructeurs avec des paramètres par défaut, tant que ces paramètres ont un sens. Les classes de la norme 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 de manière implicite à partir de ce type de paramètre. Consultez ce fil pour plus d'informations.

Autres conseils

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 aux constructeurs, mais aussi aux méthodes et aux fonctions.

Utiliser les paramètres par défaut?

La bonne chose est que vous n'aurez pas besoin de surcharger 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, c'est que vous devez déclarer votre valeur par défaut dans l'en-tête, ce qui crée une dépendance caché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 recompilez toutes les sources en utilisant cet en-tête pour vous assurer qu'elles utiliseront la nouvelle valeur par défaut.

Sinon, les sources continueront d'utiliser l'ancienne valeur par défaut.

en utilisant des constructeurs / méthodes / fonctions surchargés?

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 par la suite, j'utilise la classe et je suis surpris lorsque la valeur par défaut entre en jeu. Je ne pense donc pas vraiment que ce soit le cas. une bonne idée; Mieux vaut avoir un className :: className () puis un className :: init ( liste_glises ). Juste pour cet avantage de maintenabilité.

La réponse de Sam donne la raison pour laquelle les arguments par défaut sont préférables pour les constructeurs que la surcharge. Je veux juste ajouter que C ++ - 0x autorisera la délégation de l'un constructeur à un autre, éliminant 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.

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

Si la création de constructeurs avec des arguments est mauvaise (comme beaucoup le diraient), leur création 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 disposés à envelopper tout "nouveau". des appels à l’intérieur de blocs d’essai, ou en définissant des paramètres "is-initialisé". La variable membre, qui est une sorte de sale bidouille.

Par conséquent, le seul moyen de vous assurer que les arguments passés dans 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. En outre, la plupart des programmeurs sont habitués à concevoir une API de la même manière qu’elle est utilisée - c’est particulièrement le cas des API non publiques utilisées dans une organisation où la documentation formelle n’existe peut-être pas. Lorsque les autres programmeurs constatent que la majorité des appels ne contiennent aucun argument, ils en font de même, en restant parfaitement inconscients du comportement par défaut que vos arguments par défaut leur imposent.

Il convient également de noter que le le guide de style google C ++ évite les deux Arguments ctor (sauf si absolument nécessaire) et arguments par défaut des fonctions ou méthodes .

Je choisirais 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 que l'objet ne soit initialisé? Avoir un acteur commun serait la meilleure solution.

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.

Choix principalement personnel. Cependant, la surcharge peut faire tout ce que le paramètre par défaut peut faire, mais pas inversement.

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 question de style, mais comme Matt l'a dit, envisagez certainement de marquer les constructeurs avec des arguments par défaut qui autoriseraient la conversion implicite comme "explicite" afin d'éviter une conversion automatique non intentionnelle. 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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top