Bonjour, j'ai encore une question à la *** à vous poser
Si j'ai un pointeur vers un tableau dans mon objet, suis-je obligé de surdéfinir l'opérateur = pour pouvoir copier mon objet correctement ?
Bonjour, j'ai encore une question à la *** à vous poser
Si j'ai un pointeur vers un tableau dans mon objet, suis-je obligé de surdéfinir l'opérateur = pour pouvoir copier mon objet correctement ?
Tout dépend, veux-tu copier ton objet ou seulement ta collection de références ? Comment comptes-tu exploiter la copie du tableau ?
Je veux juste avoir un second objet qui est une copie conforme du premier.
L'opérateur = par défaut fera appel à l'opérateur = de chaque membre.
Si tu as un pointeur, il copiera le pointeur - pas les données pointées.
Donc à ta question : ça dépend de ce que tu veux faire et fais.
Comme par exemple les problèmes usuels d'appartenance/responsabilités des données du pointeur - et par extension du pointeur donc.
Et nous on en sait rien.
Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
Un peu de programmation réseau ?
Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.
LA question étant de savoir si tu as vraiment besoin d'un pointeur?
Sans plus de détails, en général on dira que "oui".
Comme le préconise la fameuse désormais "règle des cinq"
http://en.cppreference.com/w/cpp/language/rule_of_three
Jette un coup d'oeil au std::shared_ptr<>, il pourrait te servir.
Ce que tu dois faire, dépend de la stratégie de vie ton tableau.
Si le tableau est statique (pas alloué dynamiquement), il faut laisser les pointeurs nus se copier et les 2 objets partageront le même tableau.
Sinon, il faut impérativement un tableau alloué dynamiquement et :
* tu veux que la copie produise un autre tableau ayant initialement le même contenu, utilise un std::unique_ptr et tu devras implémenter un opérateur et un constructeur de copie qui fabriqueront la copie.
* tu veux que la copie partage le même tableau, utilise un std::shared_ptr, le système produira tout seul une copie qui fonctionne.
* tu veux autre chose, tu devras gérer proprement les allocations à ta manière et par exemple utiliser des pointeurs nus mais il faut bien poser le problème et agir en pro.
Merci pour vos réponses.
J'ai opté pour la surcharge de l'opérateur =, c'était le plus rapide et le plus propre pour avoir des tableaux et pointeurs séparés.
Faudra juste pas que j'oublie de rajouter chaque nouvelles variables dans ma surcharge mais à part ça c'est nickel
Salut,C'est peut être la solution (à condition que tu ait correctement implémenté l'opérateur =, et que tu aies pensé à implémenter correctement le constructeur de copie)... Ou non.
Comme on te l'as dit : tout dépend de la stratégie de gestion que tu veux mettre en place.
Et les deux toutes premières questions à se poser sont: as tu réellement besoin d'un pointeur une référence ne serait-elle pas largement préférable, vu qu'elle apporte la garantie de non nullité que ne peut pas apporter le pointeur
Imaginons plusieurs cas :
Soit tu as un tableau qui existe en dehors de ta classe, et tu veux pouvoir y accéder depuis n'importe quelle instance de ta classe : Dans ce cas, tu as sans doute intérêt à travailler avec une référence, sans doute sous une forme proche de
La deuxième série de question qu'il faut se poser, c'est : si tu utilises un pointeur, ne serait-ce pas parce que tu envisages d'avoir recours à la gestion manuelle de la mémoire, sous une forme qui pourrait être proche de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 class MaClasse{ public: explicit MaClasse(std::vector<TYPE> /* const */ & tab):tab_(tab){ } /* on n'a besoin de rien d'autre : le compilateur créera lui même le constructeur de copie, * l'opérateur d'affectation et le destructeur qui vont bien, et cela nous satisfera pleinement */ private: std::vector<TYPE> /* const */ & tab_; }; /* et l'utilisation qui va avec */ int main(){ std::vector<TYPE> tab; MaClasse a(tab); // constructeur explicite MaClasse b(a); // creation d'une copie std::vector<TYPE> t2; MaClasse c(t2); c = b; // affectation return 0; }
Si tel est le cas, le seul conseil à te donner est : oublie directement cette solution!!! :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 class MaClasse{ public: MaClasse(size_t size): size_{size},ptr_{new TYPE [size]}{ } /* il y a des trucs à prévoir ici : il faudra sans doute respecter la forme canonique orthodoxe de Coplien * et donc respecter la règles des trois grands (qui est devenue la règle des cinq grands depuis) */ private: size_t size_; // la taille du tableau TYPE * ptr_; // le pointeur sur l'espace mémoire associé aux X éléments du tableau };
La gestion manuelle de la mémoire est une véritable plaie en C++, et la bibliothèque standard nous fournit tout ce qu'il faut pour que nous n'ayons jamais à nous en inquiéter; entre autre, au travers de la classe std::vector.
Mais bon, si tu ne veux absolument pas suivre ce conseil, il va falloir prendre des mesures pour garantir le bon fonctionnement de ta classe.
La première chose, c'est qu'il faudra éviter les fuites mémoire lors de la destruction de l'instance de ta classe, si bien que nous devrons donc rajouter le destructeur, sous une forme proche de
Et, comme on a défini un destructeur personnalisé, nous sommes tenus de respecter au minimum la règle des trois grands (qui est devenue la règle de cinq grand, depuis l'apparition de la sémantique de mouvement), à savoir
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 class MaClasse{ public: MaClasse(size_t size): size_{size},ptr_{new TYPE [size]}{ } ~MaClasse(){ delete [] ptr_; } private: size_t size_; // la taille du tableau TYPE * ptr_; // le pointeur sur l'espace mémoire associé aux X éléments du tableau };
La règle des cinq grands parle en outre également du constructeur de copie par déplacement et de l'opérateur d'affectation par déplacementSi, pour une raison ou une autre, nous devons définir un comportement particulier pour le constructeur de copie, pour l'opérateur d'affectation ou pour le destructeur, alors nous devons définir le comportement de ces trois notions
Car, si on ne définit pas nous même le comportement du constructeur de copie et de l'opérateur d'affectation, le compilateur va fournir une implémentation "par défaut" de ces fonctions; et leur comportement respectif se contentera de copier les données "membre à membre". La notion de taille (la donnée membre size_) ne posera aucun problème : la copie (l'affectation) se fera parfaitement pour elle. Par contre, un pointeur n'est jamais qu'une donnée numérique entière (généralement non signée) représentant l'adresse mémoire à laquelle nous trouverons une donnée du type indiqué.
Si bien que le constructeur de copie implémenté par défaut par le compilateur se contentera... de copier l'adresse mémoire représentée par le pointeur en question, et donc que deux instances de la même classe finiront par accéder à la même adresse mémoire, avec le résultat suivant :
Pour corriger ce problème, nous devons faire en sorte que chaque instance de notre classe puisse disposer d'un espace mémoire qui lui est propre. Nous devrons donc définir notre propre constructeur de copie, sous une forme qui serait proche de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 int main(){ MaClasse a(12); // on alloue dynamiquement l'espace mémoire pour représenter 12 éléments MaClasse b(a); //CRACK a.ptr_ et b.ptr_ pointent sur la même adresse mémoire } // BOUM : ~MaClasse() est appelé pour b : l'adresse pointée par b.ptr_ est libérée et rendue au système d'exploitation // ~MaClasse() est ensuite appelé pour a : on essaye de libérer l'adresse mémoire pointée par a.ptr_... Mais elle a déjà été rendue au système d'exploitation // -->double free corruption
Cette modification corrige le problème dont je vient de parler, car les instances a et b disposent toutes les deux d'un espace mémoire qui leur est propre, mais il reste un autre problème... En effet, si on écrit un code proche de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 class MaClasse{ public: explicit MaClasse(size_t size): size_{size},ptr_{new TYPE [size]}{ } MaClasse(MaClasse const & other):size_{other.size_}, ptr_{new TYPE[other.size_]}{ /* il faut copier l'ensemble des données de other.ptr_ dans this->ptr_... */ } ~MaClasse(){ delete [] ptr_; } private: size_t size_; // la taille du tableau TYPE * ptr_; // le pointeur sur l'espace mémoire associé aux X éléments du tableau };
Il faut donc également redéfinir l'opérateur =, et, le plus facile pour éviter les problèmes est d'implémenter ce que l'on appelle l'idiome "copy and swap" : on crée d'abord une copie de l'objet, puis on interverti les données de l'objet qui subit l'affectation et de la copie que l'on vient de créer, sous une forme proche de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 int main(){ MaClasse a(10); // un espace de 10 éléments pour a.ptr_ MaClasse b(15); // un espace de 15 éléments pour b.ptr_; b=a; // CRACK : 1- b.ptr_ pointe sur les dix mêmes éléments que a.ptr_ // 2- on perd l'adresse qui était à l'origine représentée par b.ptr_ --> // fuite mémoire /* ... */ } // BOUM : ~MaClasse() est appelé en premier pour b : l'espace mémoire commencant à b.ptr_ est rendu au système d'exploitaiton // ~MaClasse() est ensuite appelé pour a : Mais comme a.ptr_ == b.ptr_, on essaye de rendre un espace mémoire qui a déjà été rendu au système d'exploitaiton // -->double free corruption
Voilà dans le détail ce qu'il faudrait faire si tu pars du principe que chaque instance de ta classe doit être responsable de l'espace mémoire qui lui est alloué pour représenter un tableau de taille inconnue à la compilation.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 class MaClasse{ public: explicit MaClasse(size_t size): size_{size},ptr_{new TYPE [size]}{ } MaClasse(MaClasse const & other):size_{other.size_}, ptr_{new TYPE[other.size_]}{ /* il faut copier l'ensemble des données de other.ptr_ dans this->ptr_... */ } MaClasse & operator = (MaClasse const & other) { /* on crée un copie parfaite de other */ MaClasse copy(other); /* on interverti les donnée de la copie et de l'objet courant */ copy.swap(*this); /* on renvoie l'objet courant */ return *this; } // la copie est détruite ici, l'adresse mémoire qui était à l'origine allouée à l'objet courant est correctement libérée ~MaClasse(){ delete [] ptr_; } /* la vrai magie intervient dans cette fonction ci */ void swap(MaClasse & other){ std::swap(size_,other.size_); // on interverti les tailles std::swap(ptr_, other.ptr_); // et surtout les adresses mémoire qui sont pointées } private: size_t size_; // la taille du tableau TYPE * ptr_; // le pointeur sur l'espace mémoire associé aux X éléments du tableau };
NOTA: normalement, je devrais également te parler du constructeur de copie par déplacement et de l'opérateur d'affectation par déplacement... Mais comme il ne s'agit en définitive que de permettre certaines optimisations dans des situations très particulières, et que c'est déjà un sujet bien plus pointu, je crois que je vais m'en passer ici
Mais, comme je te l'ai dit : il ne faut pas partir sur ce genre d'approche : la classe std::vector fait tout cela de manière totalement transparente pour toi. Il est donc ** peut-être ** sympa de le faire une fois (pour savoir comment il faut faire), mais, au delà de l'aspect théorique et didactique (pour lequel je ne suis pas certain qu'il existe réellement), tu auras surement des choses bien plus importantes auxquelles t'attarder que cela
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Merci beaucoup, ta réponse VRAIMENT très conplète
En réalité mon pointeur n'est là que pour pouvoir initialiser la taille de mon tableau dans mon constructeur.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 class Perceptron { private: float* weights; } Perceptron::Perceptron(int numberOfInputs) { weights = new float[numberOfInputs]; for (int i = 0; i < numberOfInputs; i++) { weights[i] = ( rand()/(float)RAND_MAX ) * 2 - 1; } } int Perceptron::output(float* inputs) { float sum = 0; for (int i = 0; i < numberOfInputs; i++) { sum += inputs[i] * weights[i]; } sum += bias; return sum; }
Je m'étais déjà dis qu'un vecteur solutionnerais mon problème mais en faite j'appelle ma fonction output plus de 700 fois par seconde car je fais des calculs mathématiques énormes et je me suis dis qu'utiliser un vecteur ralentirais surement mes étapes de calculs.
Je sais pas si c'est vraiment bien de résonner comme ça mais je pense qu'un pointeur est plus rapide à manipuler qu'un vecteur.
De toute façon mon objet ne devrait pas bouger donc je vais rester comme ça et si un jour j'ai des grosses modif à faire j'envisagerais le vecteur.
Laisses tomber ce pointeur tout moisi.
Un vecteur "pré-alloué" (en spécifiant sa taille initiale) est tout aussi rapide et bien plus safe.
700 fois/ secondes, c'est vraiment peanuts.
Tu penses faux et tu peux très bien et devrais utiliser un vector au lieu du pointeur nu. Ne serait-ce que pour prendre les bonnes habitudes vu que tu débutes.
L'overhead de std::vector en release est pour ainsi dire nul (en fait il est carrément nul même si tu fais pas n'importe quoi avec les options de compilation), tant que tu l'utilises correctement (adios .at, bonjour []).
Et il suffit de voir l'implémentation de l'opérateur [] pour s'en assurer.
VS2015 sur ma plateforme actuelle implémente
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 const_reference operator[](size_type _Pos) const { // subscript nonmutable sequence #if _ITERATOR_DEBUG_LEVEL == 2 if (size() <= _Pos) { // report error _DEBUG_ERROR("vector subscript out of range"); _SCL_SECURE_OUT_OF_RANGE; } #elif _ITERATOR_DEBUG_LEVEL == 1 _SCL_SECURE_VALIDATE_RANGE(_Pos < size()); #endif /* _ITERATOR_DEBUG_LEVEL */ return (*(this->_Myfirst() + _Pos)); }
Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
Un peu de programmation réseau ?
Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.
Ouais bah vos solution de merde, hein !!!! (je rigole bien sûr)
J'ai remplacé tout mes pointeurs par des vecteurs et je dirais qu'à vu d’œil c'est 2 à 3 fois moins rapide qu'avant
Je sais ce que que vous allez dire, que c'est moi qui sait pas utiliser les vecteurs mais j'ai juste remplacé mes pointeurs et je n'initialise aucun vecteur dans ma boucle principale.
Bah de toute façon j'ai la flemme de tout remettre comme avant (même si j'ai évidemment sauvegardé mon ancien code).
Je vais aussi regarder si je peux pas utilisé une pile ou une queue ou un truc du genre qui pourrais être plus rapide et mieux adapter à mon algorithme.
En tout cas merci à vous tous, j'aime ce forum <3
Faudrait en dire plus, parce que combien de débutants j'ai vu se plaindre que vector est soit-disant plus lent alors qu'ils calculaient juste mal leur truc, lisaient à moitié et oubliaient toujours la partie "reserve" ou étaient... en debug.
Le fait est que vector est totalement inliné et similaire à un tableau nu avec les bonnes options de compilation (O2 ou O3 de mémoire).
Il s'agit aussi de la collection qui devrait toujours être le premier choix pour implémenter une collection quelconque, ne serait-ce que grâce à la contiguité des éléments elle est très performante dans la plupart des cas.
Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
Un peu de programmation réseau ?
Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.
Find me on github
Ça change quelque chose d'être mode realese ou debug sous Qt ?????
Mec je crois que tu viens de révolutionner ma vie !!!!
J'ai pas pré-allouer mes vecteurs mais je les ai remplie avec des 0, ça reviens au même, non ? (même si c'est moins "pro")
Et en fait j'ai aussi modifier des petits trucs et c'est peut-être ça qui ralentie
Désolé, je suis de mauvaise fois
Que ce soit sous Qt ou quoi ou qu'est-ce, bien sur : le code destiné au débugage est automatiquement rajouté par le compilateur en mode debug.
Pire, le code de Qt en lui-même est beaucoup plus lent en debug qu'en release, car Qt aussi rajoute un tas d'informations dans ce mode
Donc, oui, si tu ne prend pas les bonnes options de compilation, tu remarquera toujours une très sévère perte de performances quand tu es en mode debug
Quoi? tu as fais un truc du genre deMec je crois que tu viens de révolutionner ma vie !!!!
J'ai pas pré-allouer mes vecteurs mais je les ai remplie avec des 0, ça reviens au même, non ? (même si c'est moins "pro")Si c'est le cas, que crois tu que cette boule apporte comme avantage par rapport à une boucle d'ajout des valeurs correctes qui t'intéressent
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 std::vector<int> tab; for(int i=0;i<MAXSIZE; ++i ) tab.push_back(0);
Pire, là où tu aurais pu ajouter tes valeurs en une seule fois, tu te retrouve à les ajouter en deux fois : une fois pour remplire ton tableau de 0, et une fois pour le remplire avec les bonnes valeurs.
Non, la solution consiste à utiliser resize / reserve pour que le tableau sache qu'il aura besoin d'un espace mémoire suffisant pour représenter N éléments (ces fonctions agrandiront en une fois suffisamment l'espace mémoire interne de std::vector pour qu'il puisse représenter le nombre d'éléments indiqués), puis à placer les valeurs qui t'intéressent dedans.
De cette manière, à part une tout petite perte de temps (*) passée à demander l'espace mémoire nécessaire, le reste ne te demande qu'une seule boucle
(*) Et comme, grâce à elle, ton tableau ne devra plus augmenter sa taille, cette perte de temps ne se fait plus qu'une fois au lieu de se faire un nombre de fois bien plus important... Donc, tu y gagne encore
Oui, très certainementDésolé, je suis de mauvaise fois
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Une erreur que j'ai souvent vue quand des gens passent d'un pointeur à un vecteur, c'est au moment de passer un argument à une fonction. Là où le pointeur était passé par valeur, il faut généralement passer le vecteur par référence (constante ou pas selon le cas).
Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.
Tout dépend de la manière . Si comme le dit Koala, tu l'as fais avec une boucle, tu te tires une balle dans le pied, car tu payes le même coût. Par contre, tu peux le faire à la construction du vecteur avec le bon constructeur. Quand tu connais à l'avance le nombre d'éléments du vecteur, ou si tu peux le prédire de manière assez précise, il faut préallouer, car sinon tu vas payer des réallocations/déplacements pour rien.
Sinon, en terme de Debug, oui Qt sera meilleure en Release. Mais aussi la STL qui est bourrée de templates et qui, une fois compilée en release, profitera beaucoup de l'inlining.
Find me on github
Merci, maintenant que tu le dis je suis convaincu que mon ralentissement viens de là, car ça copie mes vecteurs à chaque passage en paramètre c'est hyper long.
Pour te répondre, je fais ce code constructeur ce qui me permet d’appeler la méthode suivante plusieurs fois de suite.Envoyé par koala01
Comme ça cela m'évite de faire un clear et un pushback à chaque appel de ma fonction.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 void objet::train() { for(int i=0;i<tab.size(); ++i ) tab[i] = 42; }
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager