Citation:
Comme dit plus haut, les allocations dynamiques ne se feront pas via des fonctions du genre alloc(), le problème que vous soulevez ne pourra donc jamais se produire me semble-t-il.
Ce que vous ne semlez pas comprendre, c'est, que l'allocation dynamique soit effectuée au travers d'une fonciton comme malloc, d'un opérateur comme new ou de n'importe quel autre moyen syntaxique mis à votre disposition, elle s'effectuera forcément à l'intérieur d'une fonction, autrement, vous vous obligez à effectuer l'allocation sur des variables globales.
Donc, par définition, si vous dites que la fonciton dans laquelle l'allocation dynamique représente le scope de validité de cette allocation, la ressource devra être libérrée, au plus tard, au moment où vous quitterez le scope de cette fonction.
Citation:
Oui, il n'y a qu'un seul propriétaire, toujours, pas juste "A un instant donné".
Justement, non.
On peut ne pas accepter d'avoir deux propriétaires de la même ressource en même temps -- même si, sous certains aspects, cela peut rendre les choses bien plus compliquées -- mais cela n'implique pas forcément que le propriétaire de la ressource ne puisse absolument pas changer au fil du temps.
C'est un peu comme si vous achetiez une voiture. Vous en êtes le propriétaire légitime pendant plusieurs années, puis, pour une raison quelconque, vous décidez d'en changer alors qu'elle fonctionne parfaitement. Vous décidez donc de la revendre à quelqu'un d'autre (en occasion), afin de pouvoir acheter la nouvelle, dont vous deviendrez le propriétaire.
Si vous ne vous donnez pas la possibilité d'effectuer un tel "transfert de propriété", tout votre design, toute votre organisation des données doit être envisagée de manière totalement différente, avec des difficultés bien plus importantes de mise en oeuvre.
Je ne dit pas qu'il est impossible de concevoir vos projets de la sorte, je dis juste que cette manière de concevoir vos projets apporte beaucoup plus de complexité qui aurait pu (aurait du pouvoir) être évitée.
Pour citer Einstein lui-même, il faut
Citation:
Rendre les choses aussi complexes que nécessaires, mais guère plus
Or, en empêchant le transfert de propriété, vous rendez les choses ... beaucoup plus complexes que nécessaire. Et, finalement, pour un bénéfice beaucoup trop limitée par rapport à la complexité ajoutée.
Citation:
Excusez-moi, je n'avais pas précisé que les allocations ne sont pas faites via des fonctions, mais via une syntaxe du langage, et des allocations pourront se faire dans n'importe quelle fonction, mais c'est cette fonction qui en sera l'unique et éternel propriétaire.
Là non plus, cela ne marche pas.
Car cela signifie que la seule possibilité d'accéder à la ressource allouée dynamiquement (à l'exception de la fonction dans laquelle l'allocation s'effectue) passe par l'appel, depuis la fonction ayant alloué la ressource, de fonctions tierses.
Mais que se passe-t-il alors si la fonction qui a appelé la fonciton s'occupant d'allouer la ressource souhaite profiter de cette ressource :question: Peu importe que la fonction appelante récupère cette ressource au travers d'une valeur de retour ou d'un "paramètre de sortie", si la ressource est -- effectivement -- libérée au moment où l'exécution quitte le scope de la fonction "allocative", la ressource n'est ... tout simplement plus disponible pour la fonction appelante.
Citation:
Il n'a y pas de transfert de propriété, mais un "prêt" uniquement. Un "prêt" qui via la syntaxe, est obligatoirement "remboursé" (rendu).
Le problème, c'est que vous vous limitez alors à une logique du genre (code C++ style) de
Code:
1 2 3 4 5 6
| void foo(){
Type res = allocated_ressource;
fonction_pret_1(res);
fonction_tret_2(res);
/* il pourrait y avoir des tests, des boucles et tout ce que l'on veut */
} // res est libéré car sortie de scope |
Et j'en reviens à ma question: que se passe-t-il si la fonction qui appelle foo s'attend à récupérer la ressource :question: Si vous ne pouvez pas garantir que la fonction appelante sera en mesure de récupérér une ressource valide et, par définitive, d'en devenir le propriétaire légitime, tout votre design devra être adapté en conséquence... Avec une complexité bien supérieure à ce que vous permettrait le fait d'accepter le transfert de propriété.
Citation:
Citation:
Ce dont vous ne semblez pas avoir conscience en terme de ressource allouée dynamiquement, c'est que soit vous devez transférer aux fonctions appelées la ressource elle même sous une forme qui empêchera la fonction appelée de libérer la ressource en question (en C++, nous utiliserons de préférence une référence pour cela), soit c'est carrément le propriétaire en entier que vous devrez transférer, pour que, si vous décidez -- à un moment donné, dans la fonction appelée -- que la ressource peut effectivement être libérée, ce soit effectivement fait en demandant au propriétaire de s'en charger.
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 29 30 31 32 33 34 35
| /* Cette fonction utilise une ressource, mais ne peut décider de la libérer */
void user_no_release( Type /*const */ & ref){
/* on peut utiliser la ressource, la modifier si la référence
* n'est pas constante, mais pas décider de libérer la ressource
*/
}
/* cette fonction peut décider de libérer la ressource */
void possibly_release(std::unique_ptr<Type> & ptr){
/* some stuff here*/
if( some_condition)
ptr.release(); /* parce qu'on a le propriétaire en entier,
* on peut lui demander de libérer la ressource
*/
}
void foo(){
/* le propriétaire de la ressource n'est pas la fonction foo
* mais bien la donnée unique_pointer
*/
std::unique_ptr<Type> unique_pointer= std::make_unique<Type>(/* param*/);
/* on peut appeler une fonction utilisatrice en transférant "ce qui est pointé
* par le pointeur sous jacent
*/
user_no_release(*unique_pointer);
/* on peut aussi transmettre le propriétaire en entier */
possibly_release(unique_pointer);
/* seulement, arrivé ici, unique_pointer pointe peut être
* (si "some_condition" a été vérifiée dans la fonciton appelée)
* vers une adresse connue pour être invalide (nullptr)
* Si bien que, si on veut encore accéder au pointeur sous-jacent,
* il faut commencer par s'assurer de la validité du pointeur
*/
if(unique_pointer) // si le pointeur est valide
user_no_release(*unique_pointer) // on peut continuer à l'utiliser
} // dans tous les cas, comme on quite le scope dans lequel unique_pointer a été déclaré
// si le pointeur sous-jacent est encore valide, la ressource pointée sera libérée ici |
Par simplicité code tend à valider le fait qu'il n'y a qu'un seul et unique propriétaire, ce qui est tout à fait vrai au demeurant.
On peut donner cette ressource à une autre fonction sans soucis, elle en sera l'utilisatrice **unique**, et pourra par exemple ajouter des éléments à un tableau, mais sans en devenir le propriétaire.
uniquement pour les fonction qui seraient appelée -- de manière directe ou indirecte -- par foo.
La question reste malgré tout la même: qu'en est il de la fonction qui fera appel à foo, en particulier si elle souhaite récupérer la ressource allouée par foo :question:
Dites moi que vous ne voulez pas que la fonction qui appelle foo puisse récupérer la ressource, que vous comprenez les défis auxquels cette décision vous fera faire face, je vous foutrai la paix, et je vous laisserai vous dépêtrer avec vos problèmes (quitte à essayer de vous aider à vous en sortir).
Citation:
Ce qui sera ajouté au tableau ne sera évidemment pas désalloué en quittant la fonction qui a fait ces ajouts dans le tableau qu'elle utilise.
C'est justement là le problème :pour que la ressource allouée dans votre fonction et ajoutée à votre tableau ne soit libérée que lorsque le tableau est détruit, il faut ... transférer la propriété de la ressource au tableau.
Autrement, c'est la fonction qui s'occupe de l'allocation de la ressource qui, en tant que propriétaire, doit s'assurer de la libérer correctement.
Cette phrase à elle seule contredit tout ce que vous dite concernant l'impossibilité de transférer la propriété et valide tout ce que moi je peux dire concernant l'absolue nécessité d'autoriser le transfert de propriété.
Citation:
Le tableau sera étendu, les "ajout" étant fait dans le tableau seront sous la responsabilité du propriétaire unique du tableau.
Même pas... la responsabilité de libération des éléments du tableau doit échoire ... au tableau lui-même, pas au propriétaire du tableau.
Car, autrement, cela voudrait dire que le seul moyen de supprimer un élément et de s'assurer que la ressource sera correctement libérée serait de le faire ... dans la fonction dans laquelle le tableau existe.
Citation:
Un "ajout" n'est pas un "transfert", car "l'ajout" sera fait dans le tableau, son propriétaire les désallouera lorsque son scope se terminera. Ces "ajouts" seront également fait via la syntax, sans appel de fonctions.
Justement, non...
L'ajout d'un élément au tableau est un transfert de propriété, de la fonction qui crée l'élément vers le tableau qui le contient.
Citation:
Je comprend parfaitement votre point de vue, mais l'idée n'est vraiment pas de "mettre des verrous partout", au contraire.
Pourtant, en décidant qu'une fonciton ne peut pas transmettre la propriété de la ressource qu'elle alloue à la fonction qui l'a appelée, vous placez un énorme verrou qu'il vous sera difficile de contourner ;)
Citation:
Mais oui, j'essaye que ce langage soit "carré", sans ambiguïté, facile d'utilisation, et au code source très lisible.
Et c'est tout à votre honneur!
Le point est que, en l'état, votre réflexion me semble "manquer de profondeur", sans doute à cause d'un manque d'expérience, car vous n'envisagez pas un cas qui se présentera bien plus souvent que vous ne semblez le croire.
Citation:
Je ne prétend pas définir de nouvelles règles de "bonne utilisation".
C'est pourtant ce que vous faites en essayant de définir votre propre langage.
C'est ce que fait n'importe qui en essayant de définir un langage : définir un ensemble de règles permettant à un outil de comprendre ce que l'on attend de lui
Citation:
J'essaye que mon langage puisse intégrer de "bonnes pratiques", limitant les erreurs qu'un langage comme le C permet de faire.
Et c'est une excellente chose.
Cela ne doit juste pas se faire en mettant au placard des pratiques au sujet desquelles cinquante ans d'évolution dans les langages ont clairement démontré qu'elles étaient incontournables.
Citation:
Le "propriétaire" d'une ressource peut la laisser voyager, la laisser être utilisée (mais pas la désallouer) dans d'autres scopes, tout en restant le propriétaire. Si vous donnez les clefs de votre voiture à un ami pour quelque raison que ce soit, il est utilisateur de votre voiture, mais vous en rester le propriétaire, et il devra vous rendre les clefs.
C'est vrai lorsque vous êtes au niveau d'une fonction appelante...
Mais une fonction ne vaut que si elle est appelée. La question est donc toujours la même : que se passe-t-il si la fonction est appelée par une fonction qui souhaite récupérer la ressource allouée :question:
Citation:
Le fait que le scope de la fonction reste propriétaire de ce qu'elle a alloué, a pour but premier d'empêcher toute une série de bugs,
L'initiative est louable, certes, mais elle pose une série de problème qui seront bien plus difficile à prendre en compte et à résoudre que le simple fait d'accepter l'idée que la propriété d'une ressource allouée par une fonction puisse être prise en charge par (et donc transférée à) la fonction ayant appelé la fonction responsable de l'allocation de la ressource.
Citation:
Là, j'avoue que je ne comprend pas bien ce que vous voulez dire par "effet de bord".
On parle d'effet de bord lorsqu'une donnée issue de la fonction appelante est modifiée par la fonction appelée.
Citation:
Un "paramètre de sortie" est géré comme un autre argument d'une fonction.
non, parce que le paramètre est destiné à être modifié par la fonction appelée, et les modifications apportées à ce paramètre seront répercutées sur la donnée fournie par la fonction appelante.
Citation:
Là, j'avoue que je ne comprend pas bien ce que vous voulez dire par "effet de bord". Un "paramètre de sortie" est géré comme un autre argument d'une fonction. Il permet simplement de faire une différence "sémantique" entre 2 types des paramètres, ceux d'entrées qui sont entre () et 'constant' par défaut, et les valeurs que doit retourner une fonction, qui sont 'variables' par défaut.
Le fait est que, si une fonction a besoin d'une donnée externe (comprenez : dont elle ne peut définir par elle même la valeur à l'exécution), il vaut mieux que cette donnée soit immuable ("paramètre d'entrée" pour reprendre vos propres termes), mais que si elle doit rendre une donnée accessible à la fonction qui l'a appelée, il vaut mieux le faire en lui permettant de renvoyer cette donnée plutôt qu'en décidant de modifier une donnée fournie par la fonction appelante (vos "paramètres de sortie"), justement, à cause de l'effet de bord que le fait de modifier cette donnée fournie par la fonction appelante implique.
Citation:
tout comme en POO un argument est passé comme référence à un objet particulier. En python, cela est "explicite", via le 'self' qui est le premier argument de chaque méthode. En C++, c'est le 'this', qui est lui implicite.
Ah, OK... Je vois ce qui vous intrigue...
Le fait est que this ou self ne sont pas "vraiment" des paramètres de sortie. Enfin, si, techniquement parlant, ce sont bel et bien des paramètres de sorite. Seulement, on entre là dans le domaine des fonctions membres. Des fonctions qui n'existent que parce qu'il existe un type de donnée à partir duquel les appeler, et qui ne peuvent être appelées qu'au départ d'une instance du type de donnée en question.
Lorsque la fonction membre modifier les données internes de l'objet à partir duquel elle est appelée, il y a bien "effet de bord", cependant, il reste malgré tout limité, et s'il y avait moyen de l'éviter (entre autres, pour les types de données ayant sémantique de valeur), ce serait pas plus mal.