Envoyé par
yrazet
Hello, je me suis inscrit juste pour répondre à ce message.
Bienvenue sur le forum
A titre d'expérience, j'utilise un allocateur de debuggage en C++. Chaque allocation mémoire est tracée et si il y a oubli d'un delete, mon programme me le dit avec le numéro de ligne et l'allocation qui à été oubliée. Sur un assez gros programme, j'ai fais deux erreurs d'allocation. Pas bien réveillé, en déplaçant les allocations à un autre endroit et en oubliant de déplacer les deletes avec. L'allocateur m'a rappelé à l'ordre en me disant vous avez oublié de libérer deux blocs a tel endroit.
Sous VC++, l'allocateur de debuggage, vous mettez ça dans votre stdafx.h puis vous remplacez vos new par DBG_NEW.
1 2 3 4 5 6 7 8 9 10
|
#if _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#ifdef _DEBUG
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
#else
#define DBG_NEW new
#endif |
On l'a tous fait au moins une fois
Mais, pour être honnête, il y a "à boire et à manger" dans la pratique...
Il y a, en effet, d'énormes avantages à utiliser un tel système (ne serait-ce que le fait que l'on puisse suivre les allocations de mémoire), mais il y a d'énormes inconvénients, qui découlent tous d'un fait tout bête : tu utilises une fonction qui t'est propre (DBG_NEW), ce qui implique:- que les allocations "tièrces" (comprends : exécutée en dehors de ton projet) ne sont pas prises en compte
- qu'il est impossible, même si ton projet est correctement "saucissonné", de ne récupérer qu'une seule classe, une seule paire de fichiers d'en-tête (*.h / *.hpp) et d'implémentation (*.cpp) pour "autre chose" sans soit avoir besoin d'y ajouter <crtdb.h> (quid si un autre système similaire existe déjà dans l'autre projet ) ou sans avoir besoin d'en modifier le code pour pouvoir l'utiliser
Ce n'est -- bien sur -- absolument pas catastrophique, mais, disons que cela présente malgré tout un frein de taille à la réutilisation potentielle de ton code
Sinon, j'utilise pas les shared_ptr pour diverses raisons, si je me trompe pas, par exemple, il aura fallu attendre la v17 pour que shared ptr prenne en charge les tableaux.
Tu as raison, mais le fait est que, de toutes façons, le couple std::shared_ptr et std::weak_ptr ne devrait pas être ton choix "par défaut".
"Par défaut" (comprends: si tu n'as aucune raison de faire autrement), le meilleur choix de pointeur intelligent est d'utiilser std::unique_ptr, ne serait-ce que pour ne pas avoir à payer le coût d'un comptage de référence qui s'avère bien souvent inutile
En outre, "la logique" voudrait que l'on n'utilise l'allocation dynamique que pour les classes dites "à sémantique d'entité", étant donné que l'on dispose déjà de deux classes (std::vector et std::array) pour représenter la notion de "tableaux", et que ces deux classes fournissent (lorsqu'elles sont correctement utilisées, cela va de soi) toutes les garanties nécessaires.
Mais je les utilise pas pour d'autres raisons, parce que quand on fait une allocation on doit savoir ou et quand on en a plus besoin, ce qui élimine de facto le besoin d'un shared_ptr. Si il y a besoin c'est un peu inquiétant.
Tu as raison sur ce point de vue.
Malheureusement, les choses ne sont pas aussi simples que cela, pour une simple et bonne raison : C++ est un langage à exception.
Un code aussi simple
std::cout<<"hello world";
pouvant parfaitemet lancer une exception, il est très difficile de prévoir tous les chemins d'exécution qui pourraient nécessiter la libération d'une ressource allouée de manière dynamique.
C'est la raison de la mise en place de ce que l'on appelle des "capsules RAII" (dans la catégorie desquelles on trouve std::unique_ptr et std::shared_ptr): ce sont des données qui se trouvent -- a priori -- sur la pile, qui sont donc automatiquement détruite lorsque l'on sort de la portée dans laquelle elles ont déclarées (comme n'importe quelle donnée sur la pile), et provoquent donc automatiquement la libération de la ressource allouée de manière dynamique.
A coté de ça, j'utilise pas/peu non plus les notions "moderne" du C++. Je me limite aux containers, aux fonction de tri, peut être un ou deux autre trucs que j'ai oublié aussi.
A l'exception des pointeurs intelligents, dont on vient de parler, c'est une approche que l'on peut comprendre, mais qu'on ne va sans doute pas plébiciter
La plupart des fonctionnalités apportées par C++11 et ultérieures permettent en effet de rendre le code plus sur (là où il y avait un manque flagrant de sécurité) et au compilateur de générer du code plus rapide.
La déclaration des fonctions membre comme étant delete, par exemple, nous permet d'écrire un code proche de
1 2 3 4 5 6 7 8 9 10
| class MyEntity{
public:
/* une classe ayant sémantique d'entité ne peut être ni copiée ni assignée */
MyEntity(MyEntity const &) = delete;
MyEntity& operator=(MyEnity const &) = delete;
/* le destructeur d'une classe ayant sémantique d'entité sera soit public et virtuel, soit
* protected et non virtuel
*/
virtual ~MyEntity() /* = default */;
}; |
là où il aurait fallu attendre l'édition de liens pour se rendre compte d'un éventuel problème avec un code proche de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| /* Avant C++11, on aurait travaillé de la sorte */
class MyEntity{
public:
/* le destructeur d'une classe ayant sémantique d'entité sera soit public et virtuel, soit
* protected et non virtuel
*/
virtual ~MyEntity()
private:
/* on aurait déclaré le constructeur de copie et l'opérateur d'affectation
* dans l'accessibilité privée, et ON NE LES AURAIT SURTOUT PAS
* IMPLEMENTES
*/
MyEntity(MyEntity const &);
MyEntity & operator=(MyEntity const &);
} |
Pire encore, si, par distraction, le développeur de MyEntity venait à fournir une implémentation du constructeur de copie et / ou de l'opérateur d'affectation, cela aurait pu occasionner quelques bugs très spécifiques
De même, les mots clé override et final obligent le compilateur à s'assurer que les fonctions pour lesquelles ont redéfini le comportement existent bel et bien dans la classe de base, et qu'elles sont bel et bien virtuelles.
Il suffit de "si peu" (une étoile ou une esperluette en plus ou en moins pour le type de retour ou pour la déclaration d'un paramètre, par exemple) pour que l'on assiste à une surcharge de la fonction (qui n'est pas virtuelle) au lieu d'assister à une redéfinition de la fonction virtuelle que ce serait vraiment très dommage de ne pas forcer le compilateur à faire cette vérification
Mais même les lambda que je trouvais sympa à la base j'évite de les utiliser.
Là encore, c'est une approche que l'on peut comprendre...
Après tout, nous nous en sommes passé pendant 13 ans, en créant au besoin un foncteur spécifique à l'intérieur d'une fonction hein
Cependant, le système avait malgré tout ses limites... Et ce sont ces limites qui ont été repoussées grâce aux expressions lambda
Il est -- encore une fois -- dommage que tu restes "bloqué" par des limites qui ont pu être abolies par la nouvelle norme
Je crois on pourrait appeler mon style de programmation du C avec des classes plutôt que C++ ou C++ moderne.
Ca, c'est sans doute le plus gros problème!!!
Le C with classes n'a été développé à la base que pour servir de "fondation" au C++, parce qu'il fallait bien avoir "quelque chose" qui soit susceptible d'être compris par un compilateur C pour pouvoir générer le compilateur C++.
Mais, dés le départ, C et C++ ont été des langages différents, même si les premiers utilisateurs de C++ n'en avaient pas forcément conscience. Et, même si C++ a tout fait (du moins, au début) pour assurer une "certaine compatibilité" avec le C, la différence entre les deux langages n'a pu que s'amplifier avec le temps
Je préfère faire attention à ce que mes programmes restent lisibles et tout de suite compréhensible 6 mois plus tard même pour un programmeur Java.
Et tu as encore une fois bien raison!
Seulement, la manière dont tu t'y prend n'est peut-être pas la bonne
Ce qui n'est plus possible avec du C++ moderne, selon moi.
C'est là que tu te trompe
Les expression lambda (par exemple) sont apparues en 2006 en java. Tu ne dois donc pas avoir peur qu'un développeur java ne comprenne pas une expression proche de
[](Type t)->bool{/* ce qu'il faut faire */};
, même s'il y a une paire de crochets en plus
Même pour un programmeur C ca va devenir compliqué.
Ah oui, en C, les expressions lambda n'existent à mon sens pas encore
Mais C++ est beaucoup plus proche (malgré toutes les différences qu'il a par rapport à java) de java que de C, contrairement à ce que l'on croit
Donc, en effet, je raffole pas de ce C++ moderne, surtout dans ses dernières versions.
Chaqun verra midi à sa porte
Je trouve qu'il ajoute de la complexité à la complexité.
Au contraire, je te dirais bien que les versions de C++ d'après 2011 font tout pour essayer de supprimer une partie de la complexité, ou, du moins, pour arriver à en cacher une grosse partie
Sans parler des messages d'erreurs qui font 3 pages.
Les messages d'erreurs qui font trois pages ont toujours été dus à l'approche générique du C++. Enfin, je vais préciser:
Quand une seule erreur provoque un message d'erreur de trois pages, tu dois t'attendre à ce que ce soit du à une erreur en programmation générique.
Que tu aies 120 "faux positifs" après la seule erreur réelle de ton code, parce que le compilateur a "perdu les pédales" mais qu'il a malgré tout essayé de terminer le traitement de ton fichier d'implémentation, ca ca a toujours été
Mais même là, les nouvelles fonctionnalités tendent de résoudre le problème (je pense, entre autres, au static_assert) en faisant apparaitre en priorité un message plus clair et plus précis .
J'aurais préféré qu'on fasse évoluer le langage lui même, par exemple en ajoutant de la granularité,
Je suis mal réveillé, je ne vois pas trop de quoi tu parles...
des pointeurs de fonctions membre directement intégrés au langage sans passer par la lib,
Cela existe depuis toujours, même si ce n'est pas très utilisé
Quoi des get et des set automatiques Quelle horreur !!!!
et pourquoi pas une lib graphique
Figure toi qu'il semblerait que ce soit en cours
plutôt que de charger la lib de notions de plus en plus techniques.
La seule notion spécifique à la bibliothèque qui ait été rajoutée (en dehors de certaines classes dont on avait un réel besoin, comme les thread, la gestion du temps ou l'alléatoire), ca a été la sémantique de déplacement.
Et même la sémantique de déplacement représentait un besoin vraiment réel en termes de performances
Ca me fait penser que j'utilise pas non plus les std::string. Bon je suis programmeur win32 c'est 100% de pointeurs et autres LPCTSTR et c'est mieux je trouve.
Encore une fois, chacun verra midi à sa porte, mais LPCSTR
- c'est du C
- c'est absolument non portable
Dans un monde où windows n'est clairement plus le seul système d'exploitation utilisé, le deuxième point est particulièrement dommage
Au passage, pourquoi ne pas te tourner vers Qt, si tu fait des applications graphiques Il te suffirait d'avoir un compilateur croisé pour que tes applications fonctionnent également sous linux et ou sous mac... cela ne t'intéresse pas
Si ça persiste je pense pas aller vers du Rust, du Swift ou du GO mais plutôt vers le C.
Et tu te retrouveras avec un tas de problèmes encore bien pire que ceux auxquels tu es confronté avec java ou avec C++
C a l'énorme avantage d'être la "franqua lingua" du développement. Mais, si tu a l'habitude du C#, ce n'est vraiment pas pareil
Un langage assez simple pour peu qu'il soit correctement écrit et qui donne accès à tout, par contre ça me fera revenir dans la douleur à un langage sans classes et sans namespace, la c'est dur mais qui a dit que la programmation c'était facile?
Et pourtant, tu choisirais le seul langage qui ne propose à la base ni classes ni espaces de noms
Bonne programmation à tous
Cordialement,
Pareillement
Partager