Je suis tombé sur un exemple de "Effective C++ in an Embedded Environment" de Scott Meyers où deux façons d'utiliser les paramètres par défaut ont été décrites: l'une qui a été décrite comme coûteuse et l'autre comme une meilleure option.
Je manque l'explication de la raison pour laquelle la première option pourrait être plus coûteuse par rapport à l'autre.
void doThat(const std::string& name = "Unnamed"); // Bad
const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
Dans le premier, un std::string
est initialisé à partir du littéral "Unnamed"
à chaque fois la fonction est appelée sans argument.
Dans le second cas, l'objet defaultName
est initialisé ne fois (par fichier source), et simplement utilisé à chaque appel.
void doThat(const std::string& name = "Unnamed"); // Bad
C'est "mauvais" dans la mesure où un nouveau std::string
Avec le contenu "Unnamed"
Est créé à chaque appel de doThat()
.
Je dis "mauvais" et pas mauvais parce que l'optimisation des petites chaînes dans chaque compilateur C++ que j'ai utilisé placera les données "Unnamed"
Dans le std::string
temporaire créé sur le site de l'appel et ne pas lui allouer de stockage. Donc, dans ce cas spécifique , l'argument temporaire est peu coûteux. La norme ne nécessite pas l'optimisation des petites chaînes, mais elle est explicitement conçue pour le permettre, et chaque bibliothèque standard actuellement utilisée l'implémente.
Une chaîne plus longue entraînerait une allocation; l'optimisation des petites chaînes fonctionne uniquement sur les chaînes courtes. Les allocations sont coûteuses; si vous utilisez la règle de base selon laquelle une allocation est 1000+ fois plus chère qu'une instruction habituelle ( plusieurs microsecondes! ), vous ne serez pas loin.
const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
Ici, nous créons un defaultName
global avec le contenu "Unnamed"
. Ceci est créé au moment de l'initialisation statique. Il y a des risques ici; si doThat
est appelé au moment de l'initialisation statique ou de la destruction (avant ou après l'exécution de main
), il peut être appelé avec un defaultName
non construit ou un qui a déjà été détruit.
D'un autre côté, il n'y a aucun risque qu'une allocation de mémoire par appel se produise ici.
Maintenant, la bonne solution dans modern c ++ 17 est:
void doThat(std::string_view name = "Unnamed"); // Best
qui ne sera pas alloué même si la chaîne est longue; il ne copiera même pas la chaîne! En plus de cela, dans les cas 999/1000, il s'agit d'un remplacement instantané de l'ancienne API doThat
et il peut même améliorer les performances lorsque vous transmettez des données à doThat
et ne comptez pas sur le argument par défaut.
À ce stade, c ++ 17 la prise en charge dans l'incorporé peut ne pas être là, mais dans certains cas, cela pourrait l'être sous peu. Et l'affichage des chaînes est une augmentation de performances suffisamment importante pour qu'il existe déjà une multitude de types similaires dans la nature qui font la même chose.
Mais la leçon demeure encore; ne faites pas d'opérations coûteuses dans les arguments par défaut. Et l'allocation peut être coûteuse dans certains contextes (en particulier dans le monde intégré).
Peut-être que j'interprète mal "coûteux" (pour l'interprétation "correcte" voir l'autre réponse), mais une chose à considérer avec les paramètres par défaut est qu'ils ne s'adaptent pas bien dans des situations comme ça:
void foo(int x = 0);
void bar(int x = 0) { foo(x); }
Cela devient un cauchemar sujet aux erreurs une fois que vous ajoutez plus d'imbrication, car la valeur par défaut doit être répétée à plusieurs endroits (c'est-à-dire coûteuse dans le sens où une petite modification nécessite de changer différents endroits dans le code). La meilleure façon d'éviter cela est comme dans votre exemple:
const int foo_default = 0;
void foo(int x = foo_default);
void bar(int x = foo_default) { foo(x); } // no need to repeat the value here