Bonjour, pour m’entraîner je fais l'exercice sur le javaquarium, j'ai donc créer une classe Aquarium avec comme attribut un vector d'unique_ptr afin de pouvoir y stocker mes poissons j'ai donc aussi une methodes 'AjouterPoisson' mais je rencontre un problème: j'ai comme erreur "tentative de référencement d'une fonction supprimé" quelques soit la façon: que ça soit par référence ou par mouvement
Déjà, pourrais tu nous donner un peu plus de code (un genre de code minimum compilable qui puisse reproduire le problème), et nous indiquer l'erreur exacte que tu obtiens lors de la compilation.
Cela nous facilitera énormément la tâche pour te répondre
Ensuite, pourquoi veux tu créer un poisson (ou n'importe quoi d'autre) avant de le transmettre à la fonction de ta classe qui permettra de le sauvegarder?
Poses toi peut-être la question suivante:
Si, pour une raison ou une autre -- disons: parce que la population de l'aquarium est déjà trop importante -- l'aquarium refuse d'ajouter le poisson, y a-t-il vraiment du sens à vouloir garder le poisson qui a (déjà été créé mais qui a) été refusé par l'aquarium?
Si oui, que compte tu en faire?
Ensuite, pose toi peut-être une deuxième question
Y a-t-il la moindre différence -- au niveau de l'aquarium -- entre le fait d'ajouter un poisson et celui de rajouter une algue dans l'aquarium?
Tu te rendras sans doute compte qu'en fait, il s'agit toujours... d'y rajouter un élément, et que la logique à mettre en place reste la même
Enfin, poses-toi une troisième question, la plus importante:
Est-ce vraiment à l'utilisateur de l'aquarium de prendre la responsabilité de créer le poisson (ou l'algue) qu'il veut mettre dedans?
Et là, tu te rappelleras (ou tu apprendras peut-être en lisant cette réponse) que l'utilisateur est un imbécile distrait qui n'attend que l'occasion de faire une connerie et que, par conséquent, tu ne dois en aucun cas lui donner ce genre de responsabilité.
Après avoir murement réfléchi à la manière de faire pour permettre à l'utilisateur d'ajouter un élément (poisson ou algue) à l'aquarium tout en évitant de lui donner la responsabilité de créer cet élément, tu en viendra sans doute à la conclusion que, la création de l'élément souhaité doit se faire au niveau de la fonction qui permet d'ajouter le nouvel élément à l'aquarium, et que l'utilisateur ne doit savoir que... le type d'élément qu'il veut ajouter.
Tu devrais donc en arriver à la conclusion que tu as besoin d'une notion permettant de distinguer les algues des poissons, comme une énumération qui prendrait la forme de
enum ElementType{
SeaWeed,
Fish
};
et que c'est à la fonction de récolter l'ensemble des informations permettant de créer l'élément (en vérifiant leur cohérence) avant de faire appel à une fabrique d'éléments, sous une forme proche de
void Aquarium::ajouter(ElementType t, std::string const & name = ""){
/* seuls les poissons ont un nom, le nom n'a aucun
* sens pour les algues
*/
assert(t== Fish || (t==SeaWeed && name=="") &&
"only fishes can be named");
auto elem = Factory::create(t, name);
/* il faudra l'ajouter correctement ici */
}
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Déjà, pourrais tu nous donner un peu plus de code (un genre de code minimum compilable qui puisse reproduire le problème), et nous indiquer l'erreur exacte que tu obtiens lors de la compilation.
Cela nous facilitera énormément la tâche pour te répondre
Ensuite, pourquoi veux tu créer un poisson (ou n'importe quoi d'autre) avant de le transmettre à la fonction de ta classe qui permettra de le sauvegarder?
Poses toi peut-être la question suivante:
Si, pour une raison ou une autre -- disons: parce que la population de l'aquarium est déjà trop importante -- l'aquarium refuse d'ajouter le poisson, y a-t-il vraiment du sens à vouloir garder le poisson qui a (déjà été créé mais qui a) été refusé par l'aquarium?
Si oui, que compte tu en faire?
Ensuite, pose toi peut-être une deuxième question
Y a-t-il la moindre différence -- au niveau de l'aquarium -- entre le fait d'ajouter un poisson et celui de rajouter une algue dans l'aquarium?
Tu te rendras sans doute compte qu'en fait, il s'agit toujours... d'y rajouter un élément, et que la logique à mettre en place reste la même
Enfin, poses-toi une troisième question, la plus importante:
Est-ce vraiment à l'utilisateur de l'aquarium de prendre la responsabilité de créer le poisson (ou l'algue) qu'il veut mettre dedans?
Et là, tu te rappelleras (ou tu apprendras peut-être en lisant cette réponse) que l'utilisateur est un imbécile distrait qui n'attend que l'occasion de faire une connerie et que, par conséquent, tu ne dois en aucun cas lui donner ce genre de responsabilité.
Après avoir murement réfléchi à la manière de faire pour permettre à l'utilisateur d'ajouter un élément (poisson ou algue) à l'aquarium tout en évitant de lui donner la responsabilité de créer cet élément, tu en viendra sans doute à la conclusion que, la création de l'élément souhaité doit se faire au niveau de la fonction qui permet d'ajouter le nouvel élément à l'aquarium, et que l'utilisateur ne doit savoir que... le type d'élément qu'il veut ajouter.
Tu devrais donc en arriver à la conclusion que tu as besoin d'une notion permettant de distinguer les algues des poissons, comme une énumération qui prendrait la forme de
enum ElementType{
SeaWeed,
Fish
};
et que c'est à la fonction de récolter l'ensemble des informations permettant de créer l'élément (en vérifiant leur cohérence) avant de faire appel à une fabrique d'éléments, sous une forme proche de
void Aquarium::ajouter(ElementType t, std::string const & name = ""){
/* seuls les poissons ont un nom, le nom n'a aucun
* sens pour les algues
*/
assert(t== Fish || (t==SeaWeed && name=="") &&
"only fishes can be named");
auto elem = Factory::create(t, name);
/* il faudra l'ajouter correctement ici */
}
Merci de venir a mon secours Je peux donner l’entièreté de mon code(il n'est pas très long je vient a peine de le commencer et j'avais pour but de juste commencé a faire l'aquarium et les poissons) mais compilable non vu que j'ai cette erreur:
#include "Aquarium.h"
Aquarium::Aquarium(std::vector<std::unique_ptr<Poisson>>& Poissons) : m_Poissons(std::move(Poissons)){
assert(!std::empty(m_Poissons) && "Il doit y avoir des poissons"); // a voir
m_tour = 0;
}
void Aquarium::ajouterPoisson(std::unique_ptr<Poisson>& Poissons) {
m_Poissons.push_back(std::move(Poissons));
}
void Aquarium::PasserTour() {
m_tour++;
std::cout << "Tour: " << m_tour << std::endl;
std::cout << "il y a " << m_Poissons.size() << " Poissons dans l'aquarium" << std::endl;
for (auto const& p : m_Poissons) {
AfficherCaracteristique(*p);
}
}
Pour la responsabilité je n'ai pas bien compris ce que l'utilisateur peut bien faire comme connerie parce que dans le pire des cas le code ne se compile pas(je pense) mais c'est vrai que votre façon de faire évite de répéter du code et est beaucoup plus pratique.
Salut ! Je commence depuis quelques semaines à comprendre la sémantique de déplacement, et tout le bordel, et ça me fait pas mal galérer... J'ai lu ton post et, comme toi, j'ai pas compris l'erreur à mon premier coup d'oeil. Mais il me semble que tu ne respectes pas le principe de perfect-forwarding, la fonction std::move() prend en paramètre une r-value reference et tu lui passes une l-value reference. D'après le "reference collapsing", une r-value reference de l-value reference donne une l-value reference... Le paramètre passé à std::move n'est pas du bon type. Ce qu'il faudrait serait de caster explicitement ta l-value reference passée à la fonction ajouterPoisser en r-value reference, pour ça, je connais deux technique:
Voilà, c'est tout pour moi. Lis bien chaque article de la doc que je t'ai donné, et documente toi le plus possible à ce propos. Etant un débutant, je peux te dire que ce genre de concept bien connu du C++ n'est pas simple à comprendre (foutu langage xD), mais avec pas mal de pratique et de lecture tout se remet dans l'ordre.
=====
J'ai fait mes petits tests, mais mon message d'erreur est bien plus explicite que le tien:
/usr/bin/../lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/ext/new_allocator.h:136:23: error: call to deleted constructor of 'std::unique_ptr<int, std::default_delete<int> >'
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
Mais toujours impossible à comprendre pour un néophyte... C'est une vraie galère le C++. Concrètement j'ai compris le problème grâce à ces notes:
/home/devio/C++/Random/sources/main.cpp:145:2: note: in instantiation of function template specialization 'add_to_list<int>' requested here
add_to_list( test );
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/unique_ptr.h:397:7: note: 'unique_ptr' has been explicitly marked deleted here
unique_ptr(const unique_ptr&) = delete;
Avec la première note on voit que le problème se trouve bien dans la fonction add_to_list. Avec la deuxième note on comprend qu'on essaie de copier un objet qui utilise la sémantique d'entité. C'est à cause de notre std::move dont le paramètre est une l-value reference, je ne sais pas trop ce qui se passe dans la fonction std::move, mais elle semble donner lieu à une copie d'un objet std::unique_ptr alors que ça n'est pas possible.
Salut ! Je commence depuis quelques semaines à comprendre la sémantique de déplacement, et tout le bordel, et ça me fait pas mal galérer... J'ai lu ton post et, comme toi, j'ai pas compris l'erreur. Mais il me semble que tu ne respectes pas le principe de perfect-forwarding, la fonction std::move() prend en paramètre une r-value reference et tu lui passes une l-value reference. D'après le "reference collapsing", une r-value reference de l-value reference donne une l-value reference... Le paramètre passé à std::move n'est pas du bon type. Ce qu'il faudrait serait de caster explicitement ta l-value reference passée à la fonction ajouterPoisser en r-value reference, pour ça, je connais deux technique:
Voilà, c'est tout pour moi. Lis bien chaque article de la doc que je t'ai donné, et documente toi le plus possible à ce propos. Etant un débutant, je peux te dire que ce genre de concept bien connu du C++ n'est pas simple à comprendre (foutu langage xD), mais avec pas mal de pratique et de lecture tout se remet dans l'ordre.
C'est bien montré dans la doc que je t'ai fourni, std::move() attend une r-value reference... Et la collision de référence fait qu'une l-value reference "convertie" en r-value reference donne une l-value reference. D'ou le principe de perfect forwarding.
Oh mais c'est la suite du cours !! Je vais bouffer du ZDS moi ce soir ! Ca fait longtemps que j'attends ça.
C'est bien montré dans la doc que je t'ai fourni, std::move() attend une r-value reference... Et la collision de référence fait qu'une l-value reference "convertie" en r-value reference donne une l-value reference. D'ou le principe de perfect forwarding.
Oh mais c'est la suite du cours !! Je vais bouffer du ZDS moi ce soir ! Ca fait longtemps que j'attends ça.
- Edité par Daimyo_ il y a 2 minutes
Ca ne semble pas provenir du std::move j'ai l'erreur meme sans et pis si ca venait de ca l'erreur ressemblerais probablement a "Impossible de convertir l'argument"
De plus je ne vois pas vraiment ou c'est marqué qu'elle attend un r-value vu que c'est marqué que justement c'est un équivalent a static_cast vers une r-value "In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type." et meme dans l'exemple c'est une l-value qui est utilisé:
std::string str = "Hello"; //ici
std::vector<std::string> v;
// uses the push_back(const T&) overload, which means
// we'll incur the cost of copying str
v.push_back(str);
std::cout << "After copy, str is \"" << str << "\"\n";
// uses the rvalue reference push_back(T&&) overload,
// which means no strings will be copied; instead, the contents
// of str will be moved into the vector. This is less
// expensive, but also means str might now be empty.
v.push_back(std::move(str));//et ici
std::cout << "After move, str is \"" << str << "\"\n";
std::cout << "The contents of the vector are \"" << v[0]
<< "\", \"" << v[1] << "\"\n";
et sinon oui c'est la suite du cours mais elle est en beta donc il peut y avoir des modifications(d'ailleurs la partie sur la poo n'est pas terminé)
std::move est un cast en une rvalue et std::forward n'est utile qu'avec des templates. La notation T&& de la doc de std::move ne fait pas référence à une rvalue, mais à une référence universelle: soit une rvalue, soit une lvalue. Cela n'est valide qu'à la condition que T soit un type template pleinement déduit. Il y a un cours sur le sujet sur zds.
Pour l'erreur, elle vient de l’initialisation du vecteur. La construction avec {make_unique...} passe par le constructeur qui prend un std::initializer_list qui référence des valeurs constantes. Il est donc impossible de construire un vecteur ainsi puisque les valeurs ne pourront pas être déplacées.
(std::initializer_list est très mal fichu)
Donc, soit on enchaîne les push_back/emplace_back, soit on utilise un intermédiaire pour tout construire en 1 ligne:
T'as pas lu les liens que je t'ai donné ! En tous cas j'ai simulé la situation dans laquelle tu étais, j'ai obtenu le même problème: c'est à dire le compilo me disait bien que le constructeur de copie de std::unique_ptr avait été supprimé et que j'essayais de l'appeler. J'ai appliqué le perfect forwarding sur ma l-value reference et ça a fonctionné. Libre à toi de ne pas le faire mais voilà... (après edit: bah du coup c'est pas une solution... mais comment ça se fait que je n'ai plus d'erreur de compilation ??)
Sinon tu aurais du essayer la technique avec les pointeurs partagés car ça fonctionne, un pointeur partagé permet la copie, pas les pointeurs uniques.
Dans ce cas ci je pense que l'objet std::unique_ptr est copié et non déplacé comme tu le veux.
=====
@jo_link_noir je viens de lire ton message. D'accord je n'avais pas compris ça. Merci, du coup je formate mon avis là dessus.
Sinon je n'ai pas lu la repose de koala01 mais en général ses réponses sont au top du top.
Edit: trop tard....
jo_link_noir a écrit:
Pour l'erreur, elle vient de l’initialisation du vecteur. La construction avec {make_unique...} passe par le constructeur qui prend un std::initializer_list qui référence des valeurs constantes. Il est donc impossible de construire un vecteur ainsi puisque les valeurs ne pourront pas être déplacées.
J'étais en train de bugger la dessus. Pourquoi les valeurs sont constantes? C'est précisé nulle part AFAIK.
std::move est un cast en une rvalue et std::forward n'est utile qu'avec des templates. La notation T&& de la doc de std::move ne fait pas référence à une rvalue, mais à une référence universelle: soit une rvalue, soit une lvalue. Cela n'est valide qu'à la condition que T soit un type template pleinement déduit. Il y a un cours sur le sujet sur zds.
Pour l'erreur, elle vient de l’initialisation du vecteur. La construction avec {make_unique...} passe par le constructeur qui prend un std::initializer_list qui référence des valeurs constantes. Il est donc impossible de construire un vecteur ainsi puisque les valeurs ne pourront pas être déplacées.
(std::initializer_list est très mal fichu)
Donc, soit on enchaîne les push_back/emplace_back, soit on utilise un intermédiaire pour tout construire en 1 ligne:
test.cpp:34:55: warning: suggest parentheses around ‘&&’ within ‘||’ [-Wparentheses]
34 | assert(sexe == Sexe::Femelle || m_sexe == Sexe::Male && "Le sexe doit etre valide");
| ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Edité par jo_link_noir il y a environ 1 heure
Merci, j'y vois un peu plus clair et je ne pensais pas du tout que l'erreur viendrait de la c'est vrai que ça n'a pas l'air d’être très bien fichu c'est bon a savoir, je n'avais pas de warn(j'ai mis lv4) mais j'ai rajouter des () donc ça devrait être bon, mais je vais tout de même faire une fonction ajouter comme koala1 a montré
Pour la responsabilité je n'ai pas bien compris ce que l'utilisateur peut bien faire comme connerie parce que dans le pire des cas le code ne se compile pas(je pense)
Heuu... voyons un peu...
Que penses tu d'un essai du genre de
Type foo(Acuarium & a){
Poisson * p = new Poisson(/ ... */);
/* du code qui pourrait lancer exception ici */
a.ajouterPoisson(p);
/*...*/
}
qui ne compilera effectivement pas dans, du fait du passage par référence, mais qui compilerait sans problème si ta fonction prenait son paramètre sous la forme de std::unique_ptr &&, qui n'empêcherait pas les fuites mémoires .
Tout cela à cause de la deuxième esperluette... Voilà qui fait cher au caractère de code, tu ne trouves pas?
Et puis, de manière générale, ce n'est pas parce que "je ne vois pas quel problème pourrait survenir" que cela signifie forcément qu'il n'y aura jamais aucun problème.
Cela veut juste dire que les problèmes qui risquent d'apparaitre n'ont pas encore été identifiés
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Tentative de référencement d'une fonction supprimé
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
Eug
Eug