web-dev-qa-db-fra.com

L'utilisation de la fonction 'auto' de C ++ 11 peut-elle améliorer les performances?

Je peux comprendre pourquoi le type auto de C++ 11 améliore la correction et la maintenabilité. J'ai lu que cela pouvait aussi améliorer les performances ( Presque toujours automatique de Herb Sutter), mais je manque une bonne explication.

  • Comment auto peut-il améliorer les performances?
  • Quelqu'un peut-il donner un exemple?
225
DaBrain

auto peut améliorer les performances en en évitant les conversions implicites silencieuses. Un exemple que je trouve convaincant est le suivant.

std::map<Key, Val> m;
// ...

for (std::pair<Key, Val> const& item : m) {
    // do stuff
}

Voir le bug? Nous pensons maintenant que nous prenons avec élégance chaque élément de la carte par référence constante et que nous utilisons la nouvelle plage-pour expression pour préciser notre intention, mais en réalité, nous copions l'élément every. Ceci est dû au fait std::map<Key, Val>::value_type est std::pair<const Key, Val>, ne pas std::pair<Key, Val>. Ainsi, lorsque nous avons (implicitement):

std::pair<Key, Val> const& item = *iter;

Au lieu de prendre une référence à un objet existant et de le laisser comme cela, nous devons faire une conversion de type. Vous êtes autorisé à utiliser une référence const vers un objet (ou temporaire) d'un type différent tant qu'une conversion implicite est disponible, par exemple:

int const& i = 2.0; // perfectly OK

La conversion de type est une conversion implicite autorisée pour la même raison que vous pouvez convertir un fichier const Key à un Key, mais nous devons construire un temporaire du nouveau type afin de permettre cela. Ainsi, effectivement notre boucle fait:

std::pair<Key, Val> __tmp = *iter;       // construct a temporary of the correct type
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it

(Bien sûr, il n'y a pas réellement de __tmp objet, il n’est là que pour illustration, en réalité le temporaire non nommé est simplement lié à item pour sa durée de vie).

Je passe juste à:

for (auto const& item : m) {
    // do stuff
}

vient de nous sauver une tonne de copies - maintenant le type référencé correspond au type d'initialiseur, donc aucune conversion temporaire ou n'est nécessaire, nous pouvons simplement faire une référence directe.

302
Barry

Étant donné que auto déduit le type de l'expression d'initialisation, aucune conversion de type n'est impliquée. Combiné avec des algorithmes basés sur des modèles, cela signifie que vous pouvez obtenir un calcul plus direct que si vous deviez créer un type vous-même - en particulier lorsque vous utilisez des expressions dont vous ne pouvez pas nommer le type!

Un exemple typique vient de (ab) en utilisant std::function:

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1);  // bad
auto cmp2 = std::bind(f, _2, 10, _1);                       // good
auto cmp3 = [](T a, T b){ return f(b, 10, a); };            // also good

std::stable_partition(begin(x), end(x), cmp?);

Avec cmp2 Et cmp3, Tout l'algorithme peut intégrer l'appel de comparaison, alors que si vous construisez un objet std::function, Non seulement l'appel ne peut pas être en ligne, mais vous devez également pour passer par la recherche polymorphe à l'intérieur effacé du type de l'emballage de la fonction.

Une autre variante de ce thème est que vous pouvez dire:

auto && f = MakeAThing();

C'est toujours une référence, liée à la valeur de l'expression d'appel de fonction, et ne construit jamais d'objets supplémentaires. Si vous ne connaissiez pas le type de la valeur renvoyée, vous pourriez être obligé de construire un nouvel objet (éventuellement temporaire) via quelque chose comme T && f = MakeAThing(). (De plus, auto && Fonctionne même lorsque le type de retour n'est pas déplaçable et que la valeur de retour est une valeur.)

69
Kerrek SB

Il y a deux catégories.

auto peut éviter un effacement de type. Il existe des types innommables (tels que lambdas) et des types quasi innommables (comme le résultat de std::bind Ou un autre modèle de type expression, par exemple).

Sans auto, vous finissez par taper effacer les données jusqu’à quelque chose comme std::function. Le type effacement a des coûts.

std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};

task1 A une surcharge d'effacement de type - une allocation de tas possible, une difficulté à la ligne, et une surcharge d'appel de table de fonction virtuelle. task2 N'en a pas. Lambdas besoin auto ou d'autres formes de déduction de type à stocker sans effacement de type; d'autres types peuvent être si complexes qu'ils n'en ont besoin que dans la pratique.

Deuxièmement, vous pouvez vous tromper. Dans certains cas, le mauvais type fonctionnera apparemment parfaitement, mais provoquera une copie.

Foo const& f = expression();

se compilera si expression() renvoie Bar const& ou Bar ou même Bar&, où Foo peut être construit à partir de Bar . Un Foo temporaire sera créé, puis lié à f, et sa durée de vie sera prolongée jusqu'à ce que f disparaisse.

Le programmeur a peut-être voulu dire Bar const& f Et ne voulait pas en faire une copie, mais une copie est faite quand même.

L'exemple le plus courant est le type de *std::map<A,B>::const_iterator, Qui est std::pair<A const, B> const& Pas std::pair<A,B> const&, Mais l'erreur est une catégorie d'erreurs qui pèsent en silence sur les performances. Vous pouvez construire un std::pair<A, B> À partir d'un std::pair<const A, B>. (La clé sur une carte est const, car son édition est une mauvaise idée)

@Barry et @KerrekSB ont d'abord illustré ces deux principes dans leurs réponses. Il s’agit simplement d’une tentative de mise en évidence des deux problèmes en une seule réponse, avec une formulation qui vise le problème plutôt que d’être centrée sur un exemple.

41

Les trois réponses existantes donnent des exemples dans lesquels utiliser auto aide "le rend moins susceptible de pessimiser involontairement" , ce qui le rend effectivement "améliorer les performances".

Il y a un revers à la pièce. L'utilisation de auto avec des objets dont les opérateurs ne renvoient pas l'objet de base peut générer un code incorrect (toujours compilable et exécutable). Par exemple, cette question demande comment l'utilisation de auto a donné des résultats différents (incorrects) à l'aide de la bibliothèque Eigen, , c'est-à-dire les lignes suivantes

const auto    resAuto    = Ha + Vector3(0.,0.,j * 2.567);
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567);

std::cout << "resAuto = " << resAuto <<std::endl;
std::cout << "resVector3 = " << resVector3 <<std::endl;

a abouti à une sortie différente. Certes, ceci est principalement dû à l'évaluation paresseuse d'Eigens, mais ce code est/devrait être transparent pour l'utilisateur (de la bibliothèque).

Bien que les performances ne soient pas très affectées ici, utiliser auto pour éviter une pessimisation non intentionnelle pourrait être classé dans une optimisation prématurée, ou du moins, être une erreur;).

7
Avi Ginsburg