web-dev-qa-db-fra.com

Coût des paramètres par défaut en C ++

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
48
bentz123

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.

56
Angew
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é).

18

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
3