Bonjour, j'ai encore une question à la *** à vous poser :D
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 ?
Version imprimable
Bonjour, j'ai encore une question à la *** à vous poser :D
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.
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" :aie:
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 :question: 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 :question:
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 deCode:
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:
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), à savoirCode:
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éplacement ;)Citation:
Si, 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 deCode:
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 deCode:
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 deCode:
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:
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 ;)
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:
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. :calim2:
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:
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)); }
Ouais bah vos solution de merde, hein !!!! :mrgreen: (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. :calim2:
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.
2 choses:
- Il ne faut pas oublier de pré-allouer le vecteur (vector::reserve ou vector::resize, ou le bon constructeur, ça dépend de ton algo)
- Il ne faut pas oublier de mesure la perf en mode Release.
Assures-toi bien de ces deux points déjà et après compare les perfs ;)
Ça change quelque chose d'être mode realese ou debug sous Qt ????? 8O
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 :mouarf:
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 deCitation:
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")
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 :question:Code:
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 certainement :DCitation:
Désolé, je suis de mauvaise fois :)
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).
Tout dépend de la manière :mrgreen:. 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.
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.Citation:
Envoyé par koala01
Comme ça cela m'évite de faire un clear et un pushback à chaque appel de ma fonction.Code:
1
2
3
4
5 void objet::train() { for(int i=0;i<tab.size(); ++i ) tab[i] = 42; }