Récemment, j'ai eu ce qui suit
struct data {
std::vector<int> V;
};
data get_vector(int n)
{
std::vector<int> V(n,0);
return {V};
}
Le problème avec ce code est que lorsque la structure est créée, une copie se produit et la solution consiste à écrire return {std :: move (V)}
Existe-t-il un analyseur de linter ou de code qui détecterait de telles opérations de copie parasite? Ni cppcheck, cpplint, ni clang-tidy ne peuvent le faire.
EDIT: Plusieurs points pour clarifier ma question:
- Je sais qu'une opération de copie s'est produite car j'ai utilisé l' explorateur de compilateur et cela montre un appel à memcpy .
- Je pourrais identifier qu'une opération de copie s'est produite en regardant la norme oui. Mais ma mauvaise idée initiale était que le compilateur optimiserait cette copie. J'avais tort.
- Ce n'est (probablement) pas un problème de compilation car clang et gcc produisent du code qui produit un memcpy .
- Le memcpy peut être bon marché, mais je ne peux pas imaginer des circonstances où copier la mémoire et supprimer l'original est moins cher que de passer un pointeur par un std :: move .
- L'ajout de std :: move est une opération élémentaire. J'imagine qu'un analyseur de code pourrait suggérer cette correction.
c++
code-analysis
static-code-analysis
cppcheck
Mathieu Dutour Sikiric
la source
la source
std::vector
par quelque moyen que ce soit n'est pas ce qu'elle prétend être . Votre exemple montre une copie explicite, et il est naturel, et la bonne approche, (encore une fois à mon humble avis) d'appliquer lastd::move
fonction comme vous le suggérez si une copie n'est pas ce que vous voulez. Notez que certains compilateurs peuvent omettre la copie si les indicateurs d'optimisation sont activés et que le vecteur est inchangé.Réponses:
Je crois que vous avez la bonne observation mais la mauvaise interprétation!
La copie ne se produira pas en renvoyant la valeur, car chaque compilateur intelligent normal utilisera (N) RVO dans ce cas. Depuis C ++ 17, cela est obligatoire, vous ne pouvez donc pas voir de copie en renvoyant un vecteur généré localement à partir de la fonction.
OK, permet de jouer un peu avec
std::vector
et ce qui se passera pendant la construction ou en le remplissant étape par étape.Tout d'abord, permet de générer un type de données qui rend chaque copie ou déplacement visible comme celui-ci:
Et maintenant, commençons quelques expériences:
Que pouvons-nous observer:
Exemple 1) Nous créons un vecteur à partir d'une liste d'initialisation et nous nous attendons peut-être à voir 4 fois la construction et 4 mouvements. Mais nous en avons 4 exemplaires! Cela semble un peu mystérieux, mais la raison en est la mise en œuvre de la liste d'initialisation! Simplement, il n'est pas autorisé de se déplacer de la liste car l'itérateur de la liste est un
const T*
qui rend impossible le déplacement d'éléments de celle-ci. Une réponse détaillée sur ce sujet peut être trouvée ici: initializer_list et move semanticsExemple 2) Dans ce cas, nous obtenons une construction initiale et 4 copies de la valeur. Ce n'est rien de spécial et c'est ce à quoi nous pouvons nous attendre.
Exemple 3) Ici aussi, nous avons la construction et certains mouvements comme prévu. Avec mon implémentation stl, le vecteur croît de facteur 2 à chaque fois. Nous voyons donc une première construction, une autre et parce que le vecteur se redimensionne de 1 à 2, nous voyons le déplacement du premier élément. En ajoutant le 3, nous voyons un redimensionnement de 2 à 4 qui nécessite un déplacement des deux premiers éléments. Tout comme prévu!
Exemple 4) Maintenant, nous réservons de l'espace et remplissons plus tard. Maintenant, nous n'avons plus de copie ni de mouvement!
Dans tous les cas, nous ne voyons aucun mouvement ni copie en renvoyant le vecteur à l'appelant! (N) RVO est en cours et aucune autre action n'est requise à cette étape!
Retour à votre question:
Comme vu ci-dessus, vous pouvez introduire une classe proxy entre les deux à des fins de débogage.
Rendre le copy-ctor privé peut ne pas fonctionner dans de nombreux cas, car vous pouvez avoir des copies désirées et des cachées. Comme ci-dessus, seul le code de l'exemple 4 fonctionnera avec un copieur privé! Et je ne peux pas répondre à la question, si l'exemple 4 est le plus rapide, car nous remplissons la paix par la paix.
Désolé de ne pas pouvoir proposer de solution générale pour trouver des copies "indésirables" ici. Même si vous creusez votre code pour les appels de
memcpy
, vous ne trouverez pas tout car ilmemcpy
sera également optimisé et vous verrez directement quelques instructions d'assembleur faire le travail sans appeler votrememcpy
fonction de bibliothèque .Mon indice n'est pas de se concentrer sur un problème aussi mineur. Si vous avez de vrais problèmes de performances, prenez un profileur et mesurez. Il y a tellement de tueurs potentiels de performances, qu'investir beaucoup de temps sur une
memcpy
utilisation intempestive ne semble pas une idée aussi valable.la source
Avez-vous mis votre application complète dans l'explorateur du compilateur et avez-vous activé les optimisations? Sinon, ce que vous avez vu dans l'explorateur du compilateur pourrait ou non être ce qui se passe avec votre application.
Un problème avec le code que vous avez publié est que vous créez d'abord un
std::vector
, puis le copiez dans une instance dedata
. Il vaudrait mieux initialiserdata
avec le vecteur:De plus, si vous donnez simplement à l'explorateur du compilateur la définition de
data
etget_vector()
, et rien d'autre, il doit s'attendre au pire. Si vous lui donnez réellement du code source qui utiliseget_vector()
, alors regardez quel assembly est généré pour ce code source. Consultez cet exemple pour savoir ce que la modification ci-dessus, l'utilisation réelle et les optimisations du compilateur peuvent entraîner la production du compilateur.la source