Question

J'essaie d'utiliser std::string au lieu de char* chaque fois que cela est possible, mais je crains que les performances ne soient trop dégradées. Est-ce un bon moyen de retourner des chaînes (pas de vérification d'erreur pour être bref)?

std::string linux_settings_provider::get_home_folder() {
    return std::string(getenv("HOME"));
}

Aussi, une question connexe: lors de l'acceptation de chaînes en tant que paramètres, dois-je les recevoir sous la forme const std::string& ou const char*?

Merci.

Était-ce utile?

La solution

Renvoyez la chaîne.

Je pense que la meilleure abstraction en vaut la peine. Jusqu'à ce que vous puissiez mesurer une différence de performance significative, je dirais que c'est une micro-optimisation qui n'existe que dans votre imagination.

Il a fallu de nombreuses années pour obtenir une bonne abstraction de chaîne en C ++. Je ne crois pas que Bjarne Stroustroup, si célèbre pour son conservateur & "Ne paie que ce que vous utilisez &"; dictum, aurait permis un tueur de performance évident dans la langue. Une abstraction plus élevée est bonne.

Autres conseils

Renvoyez la chaîne, comme tout le monde le dit.

lors de l'acceptation de chaînes en tant que paramètres, dois-je les recevoir sous la forme const std::string& ou const char*?

Je dirais de prendre tous les paramètres const par référence, sauf s'ils sont assez légers pour être pris par valeur, ou dans les rares cas où vous avez besoin d'un pointeur nul pour qu'il soit une entrée valide, ce qui signifie & "; aucun des le & ci-dessus. Cette politique n'est pas spécifique aux chaînes.

Les paramètres de référence non const sont discutables, car du code appelant (sans un bon IDE), vous ne pouvez pas voir immédiatement s'ils sont passés par valeur ou par référence, et la différence est importante. Donc, le code peut ne pas être clair. Pour les paramètres const, cela ne s'applique pas. Les personnes qui lisent le code d’appel peuvent généralement supposer que ce n’est pas leur problème, elles n’auront donc besoin que de temps à autre de vérifier la signature.

Dans le cas où vous allez prendre une copie de l'argument dans la fonction, votre politique générale devrait consister à prendre l'argument par valeur. Ensuite, vous avez déjà une copie que vous pouvez utiliser et si vous l'avez copiée dans un emplacement spécifique (comme un membre de données), vous pouvez la déplacer (en C ++ 11) ou l'échanger (en C ++ 03). l'obtenir là. Cela offre au compilateur la meilleure opportunité d’optimiser les cas où l’appelant passe un objet temporaire.

Pour string en particulier, cela couvre le cas où votre fonction prend un std::string par valeur et que l'appelant spécifie comme expression d'argument un littéral de chaîne ou un char* pointant vers une chaîne à terminaison nulle. Si vous preniez un <=> et le copiez dans la fonction, cela entraînerait la construction de deux chaînes.

Le coût de la copie de chaînes par valeur varie selon l'implémentation de la STL avec laquelle vous travaillez:

  • std :: string sous MSVC utilise l'optimisation de chaîne courte, de sorte que les chaînes courtes (< 16 caractères iirc) ne nécessitent aucune allocation de mémoire (elles sont stockées dans std :: string les plus longs nécessitent une allocation de tas chaque fois que la chaîne est copiée.

  • std :: string sous GCC utilise une implémentation comptée de références: lors de la construction d'un std :: string à partir d'un char *, une allocation de tas est effectuée à chaque fois, mais lors du passage d'une valeur à une fonction, un décompte de références est simplement incrémenté, en évitant l’allocation de mémoire.

En général, vous feriez mieux d'oublier ce qui précède et de renvoyer std :: strings par valeur, à moins que vous ne le fassiez des milliers de fois par seconde.

re: paramètre passant, gardez à l’esprit que passer de char * - > std :: string, mais pas de std :: string - > char *. En général, cela signifie que vous feriez mieux d'accepter une référence const à un std :: string. Cependant, la meilleure justification pour accepter un const std :: string & Amp; comme argument, alors l'appelé n'a pas besoin de code supplémentaire pour la vérification par rapport à null.

Cela semble être une bonne idée.

Si cela ne fait pas partie d'un logiciel en temps réel (comme un jeu) mais d'une application normale, tout devrait bien se passer.

N'oubliez pas, & "; L'optimisation prématurée est la racine de tous les maux "

C’est la nature humaine de s’inquiéter des performances, en particulier lorsque le langage de programmation prend en charge l’optimisation de bas niveau. Ce que nous ne devrions pas oublier en tant que programmeurs, c’est que la performance des programmes n’est qu’une chose parmi d’autres que nous pouvons optimiser et admirer. En plus de la vitesse du programme, nous pouvons trouver la beauté dans notre propre performance. Nous pouvons minimiser nos efforts tout en essayant de maximiser la sortie visuelle et l'interactivité de l'interface utilisateur. Pensez-vous que cela pourrait être plus motivant que de s’inquiéter des bits et des cycles à long terme ... Donc, oui, renvoyez string: s. Ils minimisent la taille de votre code et de vos efforts et rendent la quantité de travail que vous mettez dans moins déprimante.

Dans votre cas, l'optimisation de la valeur de retour aura lieu afin que std :: string ne soit pas copié.

Faites attention lorsque vous franchissez les limites du module.

Dans ce cas, il est préférable de renvoyer les types primitifs car les types C ++ ne sont pas nécessairement compatibles binaires entre versions différentes du même compilateur.

Je suis d'accord avec les autres affiches pour utiliser une chaîne.

Mais sachez que, selon l’agressivité de votre compilateur pour optimiser les temporaires, vous aurez probablement une charge supplémentaire (par rapport à l’utilisation d’un tableau dynamique de caractères). (Remarque: la bonne nouvelle est qu’en C ++ 0a, l’utilisation judicieuse de références rvalue ne nécessitera pas d’optimisation du compilateur pour gagner en efficacité. Les programmeurs pourront ainsi offrir des garanties de performances supplémentaires sur leur code sans s’appuyer sur la qualité de le compilateur.)

Dans votre cas, la surcharge supplémentaire vaut-elle la peine d’introduire la gestion manuelle de la mémoire? La plupart des programmeurs raisonnables seraient en désaccord - mais si votre application finissait par avoir des problèmes de performances, l'étape suivante consistait à profiler votre application - ainsi, si vous introduisez de la complexité, vous ne le faites qu'une fois que vous avez la preuve qu'il est nécessaire de l'améliorer. efficacité globale.

Quelqu'un a mentionné que l'optimisation de la valeur de retour (RVO) est sans importance ici - je ne suis pas d'accord.

Le texte standard (C ++ 03) décrit ceci (12.2):

[commencer le devis standard]

  

Les temporaires de type classe sont créés dans différents contextes: liaison d’une valeur à une référence (8.5.3), renvoi d’une valeur (6.6.3), conversion générant une valeur (4.1, 5.2.9, 5.2.11). , 5.4), en lançant une exception (15.1), en entrant dans un gestionnaire (15.3) et dans certaines initialisations (8.5). [Remarque: la durée de vie des objets d'exception est décrite dans 15.1. ] Même lorsque la création de l’objet temporaire est évitée (12.8), toute la sémantique   les restrictions doivent être respectées comme si l'objet temporaire était créé. [Exemple: même si le constructeur de copie n'est pas appelé, toutes les restrictions sémantiques, telles que l'accessibilité (clause 11), doivent être satisfaites. ]

 [Example:  
struct X {
  X(int);
  X(const X&);
  ˜X();
};

X f(X);

void g()
{
  X a(1);
  X b = f(X(2));
  a = f(a);
}
  

Ici, une implémentation pourrait utiliser un temporaire dans lequel construire X (2) avant de le passer à f () en utilisant X & # 8217; copy-constructor; sinon, X (2) pourrait être construit dans l'espace utilisé pour contenir l'argument. De plus, un temporaire peut être utilisé pour conserver le résultat de f (X (2)) avant de le copier sur b en utilisant X & # 8217; copyconstructor; sinon, le résultat de f () & # 8217; s pourrait être construit dans b. Par ailleurs, l’expression a = f (a) nécessite un paramètre temporaire pour l’argument a ou le résultat de f (a) afin d’éviter les alias non souhaités de   une. ]

[Fin du devis standard]

Essentiellement, le texte ci-dessus indique que vous pouvez éventuellement compter sur RVO dans les situations d’initialisation, mais pas dans les situations d’attribution. La raison en est que, lorsque vous initialisez un objet, il n’existe aucun moyen de lui attribuer un alias sur l’objet lui-même (c’est pourquoi vous n’effectuez jamais une auto-vérification dans un constructeur de copie), mais lorsque vous le faites. une cession, il pourrait.

Il n'y a rien dans votre code qui interdit de manière inhérente RVO - mais lisez la documentation de votre compilateur pour vous assurer que vous pouvez vraiment vous en fier, si vous en avez vraiment besoin.

Je suis d'accord avec duffymo. Vous devez d'abord créer une application fonctionnelle compréhensible, puis, si nécessaire, une optimisation des attaques. C’est à ce stade que vous saurez où se trouvent les principaux goulots d’étranglement et pourrez gérer plus efficacement votre temps pour créer une application plus rapide.

Je suis d'accord avec @duffymo. N'optimisez pas tant que vous n'avez pas mesuré, ceci est également vrai lorsque vous effectuez des micro-optimisations. Et toujours: mesurez avant et après que vous avez optimisé pour voir si vous avez réellement amélioré la situation.

Renvoyez la chaîne, ce n'est pas une grosse perte de performance mais cela facilitera sûrement votre travail par la suite.

De plus, vous pouvez toujours intégrer la fonction, mais la plupart des optimiseurs l’apporteront malgré tout.

Si vous transmettez une chaîne référencée et que vous travaillez sur cette chaîne, vous n'avez rien à renvoyer. ;)

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