Dans la prolongation de mon précédent post (et bien que le sujet n´ait rien à voir) j´essayais de réécrire des fonctions d´allocation (et libération) de la mémoire.
Si cette partie ne pose pas de problème, je m´interroge plus sur l´interfacage de ces fonctions, dont je voulais qu´il soit le plus simple possible.
Alors j´utilise un truc qui ressemble à ça :
template<class T> T* __alloc()
{
...
return (T*)UnPointeurValide;
}
et il suffit dans le programme d´écrire :
int* i=alloc(int);
c´est presque aussi léger qu´avec un new donc c´est pas mal.
Seulement, là où ça bloque c´est que si je veux allouer un tableau et que je passe en argument int[10] par exemple, l´allocation se fait parfaitement, mais alors T* vaut *int[10] et ça ce n´est plus ce qu´il me faut (il me faudrait juste int*).
Une autre possibilité serait de renvoyer toujours un (void*), mais apparemment, il ne peut pas y avoir de conversion implicite de void* vers int* ou autre.
Et pourtant, dans le crt de VC++, on trouve
void * operator new( size_t cb )
et dans ces cas là le compilo accepte, mais si ma fonction renvoie un void*, il ne veut pas (c´est même pas un warning, mais une vraie erreur (cannot convert from ´void*´ to ´int*´)).
donc j´aimerais savoir si quelqu´un a une méthode qui me permettrait de traiter cesdeux cas (bien sûr, je pourrais avoir une fonction pour allouer les tableaux et une pour allouer les variables simples, mais c´est un peu moins satisfaisant, et je pense que dans l´arsenal du C++, quelque chose doit bien permettre de traiter proprement mon problème) .
merci d´avance pour vos réponses.
pourquoi je ne pas passer un parametre de nombre d´objet alloué qui aurait une valeur par défaut a 1 ?
Dans la prolongation de mon précédent post (et bien que le sujet n´ait rien à voir) j´essayais de réécrire des fonctions d´allocation (et libération) de la mémoire.
En general, il est bcp plus judicieux de reimplementer new / malloc, ou au pire d utiliser une fonction qui prends un size_t en parametre.
c´est presque aussi léger qu´avec un new donc c´est pas mal.
C´est pas du tout top comme solution. Tu va avoir une implementation par type, alors que seule la valeur de retour differe. Il est bcp plus judicieux d avoir un truc du style:
void *myAlloc( size_t size ) { ... }
template< typename T > inline T* Alloc() { return reinterpret_cast<T*>(myAlloc(sizeof(T))); }
Seulement, là où ça bloque c´est que si je veux allouer un tableau et que je passe en argument int[10] par exemple, l´allocation se fait parfaitement, mais alors T* vaut *int[10] et ça ce n´est plus ce qu´il me faut (il me faudrait juste int*).
C´est normal avec ton implementation.
Lorsque T==int, sizeof(T)=4 (par ex), type de retour T*==int*. Par contre lorsque T==int[10] (note que c est *pas* int ou int*, donc pour chaque taille (que tu rentres a la main d ailleurs) tu auras une nouvelle implementation), sizeof(T)=4*10, mais type de retour T*=*int[10]
Meme si tu utilise des typetraits, tu ne peut pas a ma connaissance recuperer T pour un type T[x], c est mort, sauf peut etre en essayant avec des typelist et des templates class avec recursion sur la taille. Pas top, en plus ca ralenti la compilation..
Et pourtant, dans le crt de VC++, on trouve void * operator new( size_t cb )
Oui mais la faut pas confondre. Un operateur new ne fait que *allouer* de la memoire (c est pour ca que d ailleurs tu ne fait rien lorsque tu overload un placement new pour un nouvel allocateur de memoire), et c est une fonction qui sera appelle par l´implementation du language. Ne pas oublier que les vtables/constructeurs/etc doivent etre appeles lorsque tu utilises un operateur new, et c´est fait automatiquement. C´est aussi pour ca que c est mieux d overloader l operateur new, car la ce que tu fais c est plus overloader un malloc (dont le typage est bel est bien void* malloc(size_t);)
Pour resoudre ton probleme (donc si tu veux pas overloader new, si tu veux pas que les constructeurs soient appeles, bref ne l´utiliser que sur des POD), je te conseille plutot un truc du style:
void *myAlloc( size_t ) { ... }
ou une variante avec ellipse si ton compilateur l accepte, permettant d eviter d avoir deux defines.
Mais je te conseil plus que vivement de reimplementer new ![]()
en fait, je me sers de l´appel par macro pour stocker à chaque fois __LINE__ et __FILE__ au début du bloc alloué (juste avant en fait). Ce qui permet au débogage, de trouver toutes les zones mémoire non libéré, et de savoir d´où elle vienne.
Mais c´est vrai que je n´avais pas pensé à l´initialisation des classes.
bon, je vais regarder comment ça se passe pour surcharger new (je pensais qu´on ne pouvait le faire que pour une classe à la fois, en opérateur interne).
raaa trop tard ! .. ![]()
nan, tu peux le faire globalement et localement.
Il y a des conseils interessant a propos de la redefinition de new dans "effective C++" de scott meyers
kufa : pour te rassurer sur ta première objection ("C´est pas du tout top comme solution. Tu va avoir une implementation par type"), c´est bien ce que je fait (ce que tu propose juste après), mais pour abréger le code sur le forum, je l´avais enlevé.
je tient pas mal à l´appel au travers de macro : ça permet par exemple un mode DEBUG dans lequel ça appel mes fonction, ou un mode normal ou le véritable new est appelé (ce qui n´est pas aussi simple si je réécris l´opérateur new).
Et pour les tableaux, tant pis, jaurais deux versions de mes fonctions.
en passant : est-il valide de supposer que le constructeur d´un objet globale (ou dont la portée est au moins un fichier) sera appelé avant d´entrer dans la fonction main ? et si oui, puis-je me reposer la-dessus pour exécuter du code d´initialisation (une sorte de contraire de atexit) ?
c´est bien ce que je fait (ce que tu propose juste après), mais pour abréger le code sur le forum, je l´avais enlevé.
Ho ok cool, mais je preferais le mentionner au cas ou
je tient pas mal à l´appel au travers de macro
Ha oui de toute facon je te conseille vraiment d utiliser des macros si tu redefini un operateur new. En effet n oublie pas que tu peux ajouter des parametres lorsque tu redefini ton operateur new.
Ce que je fais en general, dans le .h je defini mes operateurs new de debug, et defini des macros New qui prennent ou pas des arguments, et qui en mode debug appellent mes operateurs new ameliores, et en mode debug l operateur new normal.
Dans le .cpp, j implemente tout ca sauf l operateur new normal en mode release.
Du coup ca marche meme si les anciens operateur sont appeles (non je sais je suis pas tres clair.. ;)
Pour l ordre d initialisation des objets.. ben les objets globaux definis au scope du namespace sont initialises avant un appel a une fonction de ce namespace, et ce *par translation unit*, c est tout ce qui est garanti..
Par contre par unite de translation tu peux par extentions des compilos definir l´ordre de ces initializations, comme par ex __attribute__ ((init_priority (543))); pour gcc (il me semble que visual va tjs construire les objets par ordre d apparence).
"les objets globaux definis au scope du namespace sont initialises avant un appel a une fonction de ce namespace"
Mais est-ce que ça veut dire que si quelque part un objet globale qui est le premier à s´initialiser appel une fonction d´un namespace qui contient un objet global (mais pas encore initialisé), alors entre l´appel à la fonction et l´arrivé du pointeur d´exécution dans la fonction, l´objet du namespace va être initialisé ?
si oui, comment est-ce qu´on peut résoudre un problème d´initialisation croisé, ou deux objets ou chacun besoin d´un autre pour fonctionner.
Je veux dire : deux programmeurs écrivent chacun une interface qui doit fournir telle et telle fonctionnalités. Elles font ça grâce à un objet globale qui pour s´initialiser a besoin des fonctionnalité de l´autre interface (les programmeurs ont écrit leur code avec les spécifications de l´autres interface sans savoir comment elle serait implémenté).
Le jour où on link le tout, ça doit planter non ? le compilo ne peut pas s´en tirer.
bon c´est un peu tordu car pour implémenter ça il faudrait peut-être tomber dans des boucles infini, mais on doit pouvoir trouver.
Mais d´expérience, je sais que ça ne marche pas correctement : ma lib graphique ne peut pas être utilisée avant l´entrée dans la fonction main, sinon certain objet ne sont pas correctement initialisé, ce qui veut dire par exemple qu´on ne peut pas créer de fenêtre "globale" (il faut créer un pointeur et l´initialiser dans main).
Bien sûr des gens comme ceux de la SDL résolve le problème en forcant le point d´entrée du programme dans une foncion de la bibliothèque et en appelant main ensuite. Mais on retombe sur le même problème : si jamais deux programmeurs font ça, ça ne marche plus (c´est l´une des raisons majeures pour lesquelles je n´utilise pas la SDL d´ailleurs).
Dans le même filon, je ne trouve pas trace dans le code de la crt de VC++ du "new with placement" c´est-a-dire de la version qui se contente d´initialiser l´objet sans allouer la mémoire.
Pas étonnant car l´opérateur new se contente d´allouer cette mémoire, là il n´y a plus rien à faire.
Mais comment est-ce gérer par le compilo ?
si j´écris:
int* i=malloc(sizeof(int));
i=new (i) int;
est-ce que le compilo est assez malin pour comprendre qu´il n´y a rien à faire, ou alors du code sera généré pour cette ligne ?
[5 minutes après]
après vérification sur l´assembleur généré par VC++ (plutôt que d´enlever la question, je me suis dit que la réponse pouvait intéresser quelqu´un), effectivement, aucun code n´est écrit par VC++ dans la situation où le constructeur n´a rien à faire.
[20 minutes après]
si j´écris :
int *i=new int[1];
est-ce que ce que j´obtiens est exactement la même chose qu´avec
int *i=new int; // ?
je sais que pour VC++, la réponse est oui (je veux
dire par là qu´il n´y a pas de différence entre delete et delete[] (en tout cas en mode release, pour le code de débogage, je n´en sais rien), mais est-ce que la norme donne une différence (peut-il y avoir une différence de temps d´exécution entre new T et new T[1], ou entre delete et delete[] pour un même objet ?)
globalement, tout ce que dit la norme est plus stricte que la réalité (dans VC++ new et malloc et delete et free sont interchangeable ainsi que delete et delete[], contrairement à ce que dit la norme (mais dans ce sens là, c´est pas grave) puice que les versions tableau des opérateurs du C++ sont écrites sur leur versions simples qui sont elles même écrites sur les fonctions du C (presque)).
P.S. Kufa : ça n´a pas beaucoup d´importance, mais en regardant le fichier malloc.c du crt on voit que malloc et new n´utilisent pas forcement les WinHeap, mais il y a aussi une implémentation qui semble être "maison", seulement, je n´ai pas trouvé le paramètre qui lui fait utiliser l´une des implémentations plutôt que l´autre.
bon, ce formulaire doit avoir expiré 10 fois, mais pour avancer dans la résolution de mes problèmes, ça m´aide déjà beaucoup de les écrire ...
Mais est-ce que ça veut dire que si quelque part un objet globale qui est le premier à s´initialiser appel une fonction d´un namespace qui contient un objet global (mais pas encore initialisé), alors entre l´appel à la fonction et l´arrivé du pointeur d´exécution dans la fonction, l´objet du namespace va être initialisé ?
Oui exactement, enfin d apres la norme. Certains compilos vont initialiser tous les objets en meme temps.
si oui, comment est-ce qu´on peut résoudre un problème d´initialisation croisé, ou deux objets ou chacun besoin d´un autre pour fonctionner.
Avoir du code comme ca c est pas tres propre, et chiant a resoudre ![]()
Il y a pleins de facons de resoudre ce genre de probleme, mais c est plus au cas par cas. Si t as un exemple sous la main je peux te montrer comment contourner le probleme.
Sinon je te conseil vivement la lecture de
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.15
mais on doit pouvoir trouver.
Il y a effectivement pleins de techniques ![]()
Pour la creation de ta fenetre, il suffit de s assurer que la lib est cree, est c est possible. Par exemple si dans un constructeur tu appelle une fonction de ta lib graphique, ben appelle un InitGfx() avant, utilises un singleton, etc..
Pas étonnant car l´opérateur new se contente d´allouer cette mémoire, là il n´y a plus rien à faire.
Houla faut bien comprendre ce qui se passe. Lorsque tu appelles un operateur new, cela va appeler l implementation de l operateur new pour allouer la memoire, puis (et surtout) appeler le constructeur de l objet, initializer les vtables, retourner le bon type, etc.
int* i=malloc(sizeof(int));
i=new (i) int;
est-ce que le compilo est assez malin pour comprendre qu´il n´y a rien à faire, ou alors du code sera généré pour cette ligne ?
Oui le placement new est defini dans <new> ou un header comme ca, et ca ressemble a ca (je te passe les NO_THROW etc)
inline void *operator new( size_t, void *buffer ) { return buffer; }
Lorsque tu l utilise comme dans ton exemple, le constructeur de l objet/l initialization des vtables/etc sera effectuee.
il n´y a pas de différence entre delete et delete[]
Pour les pods, ca depends des implementations. J ai deja fait des operateur new et new[] differents, pour utiliser un pool de memoire different. La norme insiste sur le fait que c est different, meme si certains compilos l implemente de la meme maniere. Mais rien ne garanti que cela fonctionne tjs.
Pour les autres types, oublie: delete-> un destructeur appele, delete[] -> tous ceux du tableau.
globalement, tout ce que dit la norme est plus stricte que la réalité (dans VC++ new et malloc et delete et free sont interchangeable ainsi que delete et delete[], contrairement à ce que dit la norme (mais dans ce sens là, c´est pas grave) puice que les versions tableau des opérateurs du C++ sont écrites sur leur versions simples qui sont elles même écrites sur les fonctions du C (presque)).
Non pas du tout. Ne pas oublier que new/delete apporte des initialisations en plus.
De plus, tu peux overloader les operateurs new/delete pour des classes seulement, ce qui n est pas possible a faire avec malloc.
Pour le coup du winheap, j ai regarde l autre jour, apperement c est tjs heapalloc qui est appele a la fin.
/k
oui, j´étais un peu brouillon, mais ce que je voulais dire à la fin de mon message, c´est que d´un point du vue gestion de la mémoire, ces fonctions ou opérateurs étaient équivalent.
Mai c´est vrai (surtout pour delete et delete[]) que je n´avais pas pensé au problème de la destruction des objets.
il y a une difference notable entre le new en tant que "operateur", et le new en tant que "mot clef" ; comme explique par kufa, le premier etant une alloc memoire, le second etant une fonctionnalite "built-in" dans les compilos qui font plusieurs (allocs + appel de ctor, etc.)
j´avais ecris un truc pour mes programmeurs qui semble-t-il a pas mal servi, et a d´autre aussi :
http://www.rafb.net/paste/results/fBKsjG59.html