Bonjour,
Je cherche à faire un petit code en remplaçant les pointeurs par des shared_ptr. J’ai probablement un défaut de design induit par mon expérience des pointeurs historiques… Voici un bout de code qui met en avant mon soucis :
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class MyInt {
public:
static void init(int n);
static MyInt &get(int n);
int get_id() {return id;}
protected:
MyInt(int n):id(n){}
private:
int id;
static vector<MyInt> MyInts;
};
vector<MyInt> MyInt::MyInts;
void MyInt::init(int n)
{
MyInts.reserve(n);
for (int i=0;i<n;i++)
MyInts.push_back(MyInt(i));
}
MyInt & MyInt::get(int n)
{
return MyInts[n];
}
int main ()
{
int N;
cout << "Nb de MyInt : ";
cin >> N;
N = max(4,N); /* Pour l'exemple */
MyInt::init(N);
// Pernons un share pointer sur l'element 3.
auto p1 = make_shared<MyInt>(MyInt::get(3));
auto p2 = make_shared<MyInt>(MyInt::get(3));
cout << "p1 : id = " << p1->get_id() << ", n_ptr = " << p1.use_count() << ", addr = " << p1.get() << endl;
cout << "p2 : id = " << p2->get_id() << ", n_ptr = " << p2.use_count() << ", addr = " << p2.get() << endl;
}
Voici ce que donne l’exécution.
Nb de MyInt : 5
p1 : id = 3, n_ptr = 1, addr = 0x87a04c
p2 : id = 3, n_ptr = 1, addr = 0x87a07c
Le résultat est que mes deux pointeurs n’ont pas la même valeur, là où je voudrais qu’ils partagent le compteur de référence. Je n’avais pas très envie de mettre un vector<shared_ptr<MyInt>>
en type. C’est peut-être obligatoire…
Sinon, j’imaginais une sorte de get_shared
qui irait chercher si un shared_ptr pointe déjà sur l’objet ou s’il faut un créer un nouveau.
À votre avis ?
# Mon avis mais je ne connais rien au C++
Posté par TheBreton . Évalué à 1.
Ca va peut être faire avancer le schimlblick, ou pas…
mais il me semble que la fonction MyInt::get ne retourne pas ce qu'elle devrait
->return MyInts[n];
Moi j'imagine qu'il faut mettre
->return &MyInts[n];
pour que cela fonctionne
mais j'ai peut être pas compris ce que tu voulais faire.
[^] # Re: Mon avis mais je ne connais rien au C++
Posté par Anthony Jaguenaud . Évalué à 2.
J’ai essayé, mais ça ne compile pas… il semble que make_shared alloue de la mémoire et recopie l’objet dedans.
# Commentaire supprimé
Posté par Anonyme . Évalué à 2.
Ce commentaire a été supprimé par l’équipe de modération.
[^] # Re: Constructeur de copie
Posté par Anthony Jaguenaud . Évalué à 2.
Rajouter le & devant ne résout pas le problème car ça ne compile pas.
J’ai le choix entre mettre les pointeurs historiques, ou faire un
my_ptr<MyInt>
qui me garanti juste qu’on pointe sur quelque chose de valide.J’avais pensé mettre un shared_ptr à la place du vector, mais je ne pense pas qu’on puisse ensuite pointer sur les éléments individuellement, ni y accéder via l’
operator []
.[^] # Re: Constructeur de copie
Posté par Anthony Jaguenaud . Évalué à 2.
L’operateur [] sera pour la norme 2017.
[^] # Commentaire supprimé
Posté par Anonyme . Évalué à 2.
Ce commentaire a été supprimé par l’équipe de modération.
[^] # Re: Constructeur de copie
Posté par Anthony Jaguenaud . Évalué à 2.
Ben avec gcc et clang ça ne marche pas chez moi.
[^] # Commentaire supprimé
Posté par Anonyme . Évalué à 2. Dernière modification le 04 juin 2016 à 00:51.
Ce commentaire a été supprimé par l’équipe de modération.
[^] # Re: Constructeur de copie
Posté par Anthony Jaguenaud . Évalué à 2.
J’ai essayé, mais le programme crash quand il cherche à désallouer mais objets statiques…
[^] # Commentaire supprimé
Posté par Anonyme . Évalué à 2.
Ce commentaire a été supprimé par l’équipe de modération.
# peut-être ceci ?
Posté par nazcafan . Évalué à 1.
ça rend ça :
Comme il a été dit dans les commentaires précédents,
make_shared
va construire une copie deMyint::get(3)
, et initialiser unshared_ptr
sur cette copie, pas sur le troisème élément de ton tableau.[^] # Re: peut-être ceci ?
Posté par nazcafan . Évalué à 1. Dernière modification le 03 juin 2016 à 17:58.
aussi, tu peux t'assurer facilement du fonctionnement de la mécanique de comptage en mettant
p2
dans un scope :résultat :
Nb de MyInt : 5
p1 : id = 3, n_ptr = 1, addr = 0x8289030
p1 : id = 3, n_ptr = 2, addr = 0x8289030
p2 : id = 3, n_ptr = 2, addr = 0x8289030
p1 : id = 3, n_ptr = 1, addr = 0x8289030
[^] # Re: peut-être ceci ?
Posté par Anthony Jaguenaud . Évalué à 2.
Oui, je sais que ça marche comme ça. Mais dans mon vrai code, qui est une implémentation de graph, je fais les get dans les constructeur de liens entre mes Nodes… pas à deux lignes d’intervalle comme dans l’exemple.
Bon, en gros, je continue avec des pointeurs standard… je devrais me trouver un autre exercice pour utiliser les pointeurs intelligent. Moi qui pensait qu’on allait pouvoir s’affranchir complètement des pointeurs historique…
[^] # Re: peut-être ceci ?
Posté par gorbal . Évalué à 1. Dernière modification le 03 juin 2016 à 21:34.
[^] # Re: peut-être ceci ?
Posté par Anthony Jaguenaud . Évalué à 2.
C’est quoi la bonne méthode pour pointer sur un objet static ou global en c++11 ?
Un avis sur la question ?
[^] # Re: peut-être ceci ?
Posté par nazcafan . Évalué à 2.
Mec, le but des
shared_ptr
,unique_ptr
et autres est d'implanter une notion de propriété (ownership) qui, en gros représente la responsabilité de détruire l'objet et libérer la mémoire sur laquelle il a été construit.Si l'objet est global ou qu'il est alloué sur la pile, il est détruit automatiquement quand le programme s’arrête ou quand le programme quitte le scope dans lequel il a été déclaré. En conséquence, il n'y a aucun intérêt à créer un pointeur intelligent dessus.
Si tes objets sont placés dans un graphe, cette responsabilité devrait revenir soit au graphe lui même, soit à des objets intermédiaires, par exemple les
vector
que tu utilises dans ton implantation. Su tu utilises un<vector<shared_ptr<T>>
, lesshared_ptr
auront cette responsabilité, avec un effet de bord : tu pourras alors avoir détruit ton objetgraphe
et toujours avoir desT
non-détruits si jamais tu as gardé des copies deshared_ptr
. À toi de décider si c'est une bonne ou mauvaise chose .[^] # Re: peut-être ceci ?
Posté par fearan . Évalué à 2.
Je confirme, les pointeurs intelligents ne permettent pas de s'affranchir du minimum de réflexion nécessaire avant la construction d'un objet, que ce soit new/make_shared/make_unique
S'affranchir des new & delete, oui, s'affranchir des pointeurs historique et ne balader que des pointeurs dit intelligents, j'aime moins. les shared_ptr incluent nécessairement un overhead pour chaque accès, et il pousse les gens à s'affranchir de la question "Qui possède le pointeur?",
j'ajouterai que je vois rarement un shared_ptr y compris lorsque l'on est pas sensé modifier l'objet (en même temps c'est galère à gérer. Normalement on a plus trop à balader des pointeur, et ce depuis longtemps, on balade des ref, des const ref, et si on se paye un pointeur, c'est qu'il peut être NULL (enfin de mon point de vue)
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: peut-être ceci ?
Posté par Space_e_man (site web personnel) . Évalué à 1.
Dans un graphe, les nœuds et les liens pourraient être alloués dynamiquement. Là, tu pourrais utiliser les std::shared_ptr . Est-ce toi qui implémente le graphe ?
[^] # Re: peut-être ceci ?
Posté par Anthony Jaguenaud . Évalué à 2.
Oui, mais j’ai fait simple. Comme j’ai un nombre de nœuds et de liens connu au démarrage (lecture fichier) et que c’est statique ensuite, j’ai fait un
tableauvecteur de nœuds, ensuite je créé également un vecteur de liens. Je peux construire mes liens avec des références, puisque connue à la construction et que lereserve
me garanti que les éléments de mon vecteur ne seront pas déplacé.Ce que j’ai fait n’a rien de complexe à la base, je voulais m’entrainer au c++11 pour une fois que je ne suis pas coincé par du 03 voir antérieur… du coup, c’est un fail pour mon essai des shared_ptr. Par contre j’ai progressé dans le choix de quand les utiliser ou non.
La solution de faire des vecteurs de pointeurs me semble faire de l’overengineering… donc pas de c++11 pour cette partie :'(
[^] # Re: peut-être ceci ?
Posté par Space_e_man (site web personnel) . Évalué à 2.
Pour l'ensemble des lecteurs, j'aimerais quand même résumer cela car c'est fondamentale.
Une variable globale est connue du programmeur et il "souhaitera" qu'elle soit "instanciée" avant-même le début de la fonction principale (main) et "détruite" après la sortie de cette fonction. Le programmeur ne devra normalement pas ce soucier de cela.
Une variable locale se situe dans un bloque d'instructions délimité par des accolades. La variable est instanciée à l'endroit-même de sa déclaration et détruite après la fin du bloque d'instructions, quoi qu'il en soit ! même si une exception est levée par exemple, ou un goto (à éviter par ailleurs) effectué… C'est ça l'idée, même s'il y a de rares exceptions… Là encore, le programmeur n'a pas à se faire de soucis…
Par contre une variable dynamique est instanciée suite à l'instruction new et détruite avec l'instruction delete. C'est au programmeur de ce soucier qu'à chaque new correspondra un delete opportun. Car cette variable subsistera à tout le reste, que ce soit la fin d'un bloque d'instructions, sortie de fonction, etc. Même la fin du programme, de la fonction principale ne garanti rien. L'adresse allouée de cette variable sera donc précieuse entre le new et le delete, car c'est sur base de cette adresse que le delete pourra se faire.
C'est là qu'intervienne les pointeurs intelligents, encapsuler la "responsabilité" d'une variable dynamique à l'intérieur d'une autre variable, qui par exemple, sera locale. Ainsi, même suite à la levée d'une exception, le pointeur intelligent (selon l'exemple d'une variable locale) sera détruit, et par-là-même un delete effectué sur la variable dynamique.
Quant à la transmission d'un pointeur à une fonction. Cela signifie généralement que, contrairement à une référence (principalement par convention), l'adresse à pu être stockée dans une variable subsistante à la sortie de la fonction. Ce n'est pas une obligation mais il faudra se poser la question… L'autre possibilité est que cette adresse soit utilisée comme littérateur ou comme base d'une série (array[]). On se souciera alors de savoir comment se fait le parcours et sur quelle base elle prendra fin (nombre d'éléments, adresse de fin, etc. ?). L'adresse d'une variable globale ou locale s'obtient avec l'opérateur & alors qu'un pointeur intelligent prévoira généralement une fonction membre (le fameux std::shared_ptr::get par exemple).
"Par convention" car il est possible à l'intérieur d'une fonction de récupérer l'adresse d'un variable argument de type référence et de la stocker. Mais à mon habitude, je ne m'y attendrais pas… C'est pas "intuitif" me semble-t-il…
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.