Je travaille sur mon propre moteur de jeu et je conçois actuellement mes managers. J'ai lu que pour la gestion de la mémoire, l'utilisation des fonctions Init()
et CleanUp()
est meilleure que celle des constructeurs et des destructeurs.
Je cherchais des exemples de code C ++, pour voir comment ces fonctions fonctionnent et comment je peux les implémenter dans mon moteur. Comment fonctionne Init()
et CleanUp()
fonctionne, et comment puis-je les implémenter dans mon moteur?
Réponses:
C'est assez simple, en fait:
Au lieu d'avoir un constructeur qui fait votre configuration,
... demandez à votre constructeur de faire peu ou rien du tout, et écrivez une méthode appelée
.init
ou.initialize
, qui ferait ce que votre constructeur ferait normalement.Alors maintenant, au lieu de simplement faire comme:
Tu peux y aller:
L'avantage est que vous pouvez désormais utiliser plus facilement l'injection de dépendance / l'inversion de contrôle dans vos systèmes.
Au lieu de dire
Vous pouvez construire le soldat, lui donner une méthode équipez, où vous remettez lui une arme, et appeler alors tous le reste des fonctions constructeur.
Alors maintenant, au lieu de sous-classer les ennemis où un soldat a un pistolet et un autre a un fusil et un autre a un fusil de chasse, et c'est la seule différence, vous pouvez simplement dire:
Même chose pour la destruction. Si vous avez des besoins particuliers (suppression d'écouteurs d'événements, suppression d'instances de tableaux / quelles que soient les structures avec lesquelles vous travaillez, etc.), vous les appellerez manuellement, afin de savoir exactement quand et où dans le programme qui se passait.
ÉDITER
Comme Kryotan l'a souligné, ci-dessous, cela répond au "Comment" du post original , mais ne fait pas vraiment un bon travail de "Pourquoi".
Comme vous pouvez probablement le voir dans la réponse ci-dessus, il pourrait ne pas y avoir beaucoup de différence entre:
et l'écriture
tout en ayant une fonction constructeur plus grande.
Il y a un argument à faire pour les objets qui ont 15 ou 20 conditions préalables, ce qui rendrait un constructeur très, très difficile à travailler, et cela rendrait les choses plus faciles à voir et à retenir, en tirant ces choses dans l'interface , afin que vous puissiez voir comment l'instanciation fonctionne, un niveau plus haut.
La configuration optionnelle des objets en est une extension naturelle; définir éventuellement des valeurs sur l'interface, avant d'exécuter l'objet.
JS a de très bons raccourcis pour cette idée, qui semblent tout simplement hors de propos dans les langages de type c plus forts.
Cela dit, il y a des chances, si vous avez affaire à une liste d'arguments aussi longue dans votre constructeur, que votre objet est trop grand et fait trop, tel quel. Encore une fois, c'est une chose de préférence personnelle, et il y a des exceptions partout, mais si vous passez 20 choses dans un objet, il y a de bonnes chances que vous puissiez trouver un moyen de faire en sorte que cet objet fasse moins, en faisant des objets plus petits .
Une raison plus pertinente, et largement applicable, serait que l'initialisation d'un objet repose sur des données asynchrones, que vous n'avez pas actuellement.
Vous savez que vous avez besoin de l'objet, vous allez donc le créer de toute façon, mais pour qu'il fonctionne correctement, il a besoin des données du serveur ou d'un autre fichier qu'il doit maintenant charger.
Encore une fois, que vous passiez les données nécessaires dans un gigantesque init ou que vous construisiez une interface n'est pas vraiment important pour le concept, autant qu'il l'est pour l'interface de votre objet et la conception de votre système ...
Mais en termes de construction de l'objet, vous pourriez faire quelque chose comme ceci:
async_loader
peut obtenir un nom de fichier, un nom de ressource ou autre, charger cette ressource - peut-être qu'il charge des fichiers audio ou des données d'image, ou peut-être qu'il charge des statistiques de caractères enregistrées ...... et ensuite il alimenterait ces données
obj_w_async_dependencies.init(result);
.Ce type de dynamique se retrouve fréquemment dans les applications Web.
Pas nécessairement dans la construction d'un objet, pour les applications de niveau supérieur: par exemple, les galeries peuvent se charger et s'initialiser tout de suite, puis afficher les photos au fur et à mesure - ce n'est pas vraiment une initialisation asynchrone, mais là où elle est vue plus fréquemment, ce serait dans les bibliothèques JavaScript.
Un module peut dépendre d'un autre, et donc l'initialisation de ce module peut être différée jusqu'à ce que le chargement des dépendants soit terminé.
En termes d'instances spécifiques au jeu, considérez une
Game
classe réelle .Pourquoi ne pouvons-nous pas appeler
.start
ou.run
dans le constructeur?Les ressources doivent être chargées - le reste de tout a été à peu près défini et il est bon d'y aller, mais si nous essayons d'exécuter le jeu sans connexion à la base de données, ou sans textures ou modèles ou sons ou niveaux, cela ne sera pas un jeu particulièrement intéressant ...
... alors quelle est la différence entre ce que nous voyons d'un typique
Game
, sauf que nous donnons à sa méthode "go ahead" un nom qui est plus intéressant que.init
(ou inversement, casser l'initialisation encore plus loin, pour séparer le chargement, mise en place des choses qui ont été chargées, et exécution du programme lorsque tout a été mis en place).la source
.init
, peut-être pas, mais probablement. Ergo, cas valide.Tout ce que vous avez lu qui dit qu'Init et CleanUp est meilleur, aurait également dû vous dire pourquoi. Les articles qui ne justifient pas leurs affirmations ne valent pas la peine d'être lus.
Le fait d'avoir des fonctions d'initialisation et d'arrêt séparées peut faciliter la configuration et la destruction des systèmes, car vous pouvez choisir l'ordre dans lequel les appeler, tandis que les constructeurs sont appelés exactement lorsque l'objet est créé et les destructeurs appelés lorsque l'objet est détruit. Lorsque vous avez des dépendances complexes entre 2 objets, vous avez souvent besoin que les deux existent avant de s'installer - mais souvent c'est un signe de mauvaise conception ailleurs.
Certains langages n'ont pas de destructeurs sur lesquels vous pouvez compter, car le comptage des références et la collecte des ordures rendent plus difficile de savoir quand l'objet sera détruit. Dans ces langues, vous avez presque toujours besoin d'une méthode d'arrêt / nettoyage, et certains aiment ajouter la méthode init pour la symétrie.
la source
Je pense que la meilleure raison est: de permettre la mise en commun.
si vous avez Init et CleanUp, vous pouvez, quand un objet est tué, appeler simplement CleanUp, et pousser l'objet sur une pile d'objets du même type: un 'pool'.
Ensuite, chaque fois que vous avez besoin d'un nouvel objet, vous pouvez extraire un objet du pool OU si le pool est vide - trop mauvais - vous devez en créer un nouveau. Ensuite, vous appelez Init sur cet objet.
Une bonne stratégie consiste à pré-remplir le pool avant le début du jeu avec un «bon» nombre d'objets, de sorte que vous n'avez jamais à créer d'objet groupé pendant le jeu.
Si, en revanche, vous utilisez «nouveau» et arrêtez simplement de référencer un objet lorsqu'il ne vous est d'aucune utilité, vous créez des ordures qui doivent être récupérées à un moment donné. Ce souvenir est particulièrement une mauvaise chose pour les langages à un seul thread comme Javascript, où le garbage collector arrête tout le code lorsqu'il évalue qu'il doit se souvenir de la mémoire des objets qui ne sont plus utilisés. Le jeu se bloque pendant quelques millisecondes et l'expérience de jeu est gâchée.
- Vous avez déjà compris -: si vous regroupez tous vos objets, aucun souvenir ne se produit, donc plus de ralentissement aléatoire.
Il est également beaucoup plus rapide d'appeler init sur un objet provenant du pool que d'allouer de la mémoire + init à un nouvel objet.
Mais l'amélioration de la vitesse a moins d'importance, car bien souvent la création d'objets n'est pas un goulot d'étranglement des performances ... À quelques exceptions près, comme les jeux frénétiques, les moteurs à particules ou le moteur physique utilisant intensivement des vecteurs 2D / 3D pour leurs calculs. Ici, la vitesse et la création de déchets sont grandement améliorées en utilisant un pool.
Rq: vous n'aurez peut-être pas besoin d'avoir une méthode CleanUp pour vos objets regroupés si Init () réinitialise tout.
Edit: répondre à ce post m'a motivé à finaliser un petit article que j'ai fait sur la mutualisation en Javascript .
Vous pouvez le trouver ici si vous êtes intéressé:
http://gamealchemist.wordpress.com/
la source
Votre question est inversée ... Historiquement parlant, la question la plus pertinente est:
Pourquoi est la construction + intialisation confondait , à savoir pourquoi ne pas nous ces étapes séparément? Cela va sûrement à l'encontre de SoC ?
Pour C ++, l'intention de RAII est que l'acquisition et la libération des ressources soient directement liées à la durée de vie de l'objet, dans l'espoir que cela garantira la libération des ressources. Le fait-il? Partiellement. Il est rempli à 100% dans le contexte des variables basées sur la pile / automatiques, où le fait de laisser la portée associée appelle automatiquement des destructeurs / libère ces variables (d'où le qualificatif
automatic
). Cependant, pour les variables de tas, ce modèle très utile tombe malheureusement en panne, car vous êtes toujours obligé d'appeler explicitementdelete
pour exécuter le destructeur, et si vous oubliez de le faire, vous serez toujours mordu par ce que RAII tente de résoudre; dans le contexte des variables allouées en tas, alors, C ++ offre un avantage limité sur C (delete
vsfree()
) tout en confondant construction et initialisation, ce qui a un impact négatif sur les points suivants:La construction d'un système d'objets pour les jeux / simulations en C est fortement recommandée car elle apportera beaucoup de lumière sur les limites de RAII et d'autres modèles centrés sur OO grâce à une compréhension plus approfondie des hypothèses que font C ++ et plus tard les langages OO classiques. (rappelez-vous que C ++ a commencé comme un système OO construit en C).
la source