Je connais 5 catégories générales où la recompilation d'un compilateur C ++ 03 en C ++ 11 peut entraîner des augmentations de performances illimitées qui ne sont pratiquement pas liées à la qualité de l'implémentation. Ce sont toutes des variantes de la sémantique des mouvements.
std::vector
réaffecter
struct bar{
std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03
chaque fois que le foo
est réaffecté tampon de 03 en C ++ , il copié tous vector
en bar
.
En C ++ 11, il déplace à la place le bar::data
s, qui est fondamentalement gratuit.
Dans ce cas, cela repose sur des optimisations à l'intérieur du std
conteneur vector
. Dans tous les cas ci-dessous, l'utilisation de std
conteneurs est simplement parce que ce sont des objets C ++ qui ont une move
sémantique efficace en C ++ 11 "automatiquement" lorsque vous mettez à niveau votre compilateur. Les objets qui ne le bloquent pas et qui contiennent un std
conteneur héritent également des move
constructeurs améliorés automatiques .
Échec NRVO
Lorsque NRVO (nommé l'optimisation de la valeur de retour) échoue, en C ++ 03, il retombe sur copie, sur C ++ 11, il retombe en mouvement. Les échecs de NRVO sont faciles:
std::vector<int> foo(int count){
std::vector<int> v; // oops
if (count<=0) return std::vector<int>();
v.reserve(count);
for(int i=0;i<count;++i)
v.push_back(i);
return v;
}
ou même:
std::vector<int> foo(bool which) {
std::vector<int> a, b;
// do work, filling a and b, using the other for calculations
if (which)
return a;
else
return b;
}
Nous avons trois valeurs - la valeur de retour et deux valeurs différentes dans la fonction. Elision permet de «fusionner» les valeurs de la fonction avec la valeur de retour, mais pas entre elles. Ils ne peuvent pas tous les deux être fusionnés avec la valeur de retour sans fusionner les uns avec les autres.
Le problème de base est que l'élision NRVO est fragile, et le code avec des modifications non proches du return
site peut soudainement avoir des réductions de performances massives à cet endroit sans aucun diagnostic émis. Dans la plupart des cas d'échec NRVO, C ++ 11 se termine par un move
, tandis que C ++ 03 se termine par une copie.
Renvoyer un argument de fonction
L'élision est également impossible ici:
std::set<int> func(std::set<int> in){
return in;
}
en C ++ 11 c'est pas cher: en C ++ 03 il n'y a aucun moyen d'éviter la copie. Les arguments des fonctions ne peuvent pas être élidés avec la valeur de retour, car la durée de vie et l'emplacement du paramètre et de la valeur de retour sont gérés par le code appelant.
Cependant, C ++ 11 peut passer de l'un à l'autre. (Dans un exemple moins jouet, quelque chose pourrait être fait pour le set
).
push_back
ou insert
Enfin, l'élision dans les conteneurs ne se produit pas, mais les surcharges C ++ 11 rvalue déplacent les opérateurs d'insertion, ce qui économise des copies.
struct whatever {
std::string data;
int count;
whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );
en C ++ 03 un temporaire whatever
est créé, puis il est copié dans le vecteur v
. 2 std::string
tampons sont alloués, chacun avec des données identiques, et un est rejeté.
En C ++ 11, un temporaire whatever
est créé. La whatever&&
push_back
surcharge est alors move
temporaire dans le vecteur v
. Un std::string
tampon est alloué et déplacé dans le vecteur. Un vide std::string
est jeté.
Affectation
Volé de la réponse de @ Jarod42 ci-dessous.
L'élision ne peut pas se produire avec l'affectation, mais le déplacement peut.
std::set<int> some_function();
std::set<int> some_value;
// code
some_value = some_function();
some_function
renvoie ici un candidat à élider, mais comme il n'est pas utilisé pour construire directement un objet, il ne peut pas être élidé. En C ++ 03, les résultats ci-dessus entraînent la copie du contenu du temporaire some_value
. En C ++ 11, il est déplacé vers some_value
, qui est fondamentalement gratuit.
Pour le plein effet de ce qui précède, vous avez besoin d'un compilateur qui synthétise les constructeurs de mouvements et les affectations pour vous.
MSVC 2013 implémente les constructeurs de déplacement dans des std
conteneurs, mais ne synthétise pas les constructeurs de déplacement sur vos types.
Les types contenant std::vector
s et similaires n'obtiennent donc pas de telles améliorations dans MSVC2013, mais commenceront à les obtenir dans MSVC2015.
clang et gcc ont depuis longtemps implémenté des constructeurs de mouvements implicites. Le compilateur d'Intel 2013 prendra en charge la génération implicite de constructeurs de déplacement si vous réussissez -Qoption,cpp,--gen_move_operations
(ils ne le font pas par défaut dans un effort de compatibilité croisée avec MSVC2013).