Je viens d'un arrière-plan Java et j'ai commencé à travailler avec des objets en C ++. Mais une chose qui m'est venue à l'esprit est que les gens utilisent souvent des pointeurs vers des objets plutôt que les objets eux-mêmes, par exemple cette déclaration:
Object *myObject = new Object;
plutôt que:
Object myObject;
Ou au lieu d'utiliser une fonction, disons testFunc()
, comme ceci:
myObject.testFunc();
nous devons écrire:
myObject->testFunc();
Mais je ne peux pas comprendre pourquoi devrions-nous procéder de cette façon. Je suppose que cela a à voir avec l'efficacité et la vitesse car nous avons un accès direct à l'adresse mémoire. Ai-je raison?
Réponses:
Il est très regrettable que vous voyiez l'allocation dynamique si souvent. Cela montre simplement le nombre de mauvais programmeurs C ++.
Dans un sens, vous avez deux questions regroupées en une seule. La première est quand devrions-nous utiliser l'allocation dynamique (en utilisant
new
)? La seconde est quand devrions-nous utiliser des pointeurs?Le message important à retenir est que vous devez toujours utiliser l'outil approprié pour le travail . Dans presque toutes les situations, il y a quelque chose de plus approprié et de plus sûr que d'effectuer l'allocation dynamique manuelle et / ou d'utiliser des pointeurs bruts.
Allocation dynamique
Dans votre question, vous avez montré deux façons de créer un objet. La principale différence est la durée de stockage de l'objet. Lors d'une opération
Object myObject;
dans un bloc, l'objet est créé avec une durée de stockage automatique, ce qui signifie qu'il sera détruit automatiquement lorsqu'il sortira du cadre. Lorsque vous le faitesnew Object()
, l'objet a une durée de stockage dynamique, ce qui signifie qu'il reste en vie jusqu'à ce que vous le explicitiezdelete
. Vous ne devez utiliser la durée de stockage dynamique que lorsque vous en avez besoin. Autrement dit, vous devriez toujours préférer créer des objets avec une durée de stockage automatique lorsque vous le pouvez .Les deux principales situations dans lesquelles vous pourriez avoir besoin d'une allocation dynamique:
Lorsque vous avez absolument besoin d'une allocation dynamique, vous devez l'encapsuler dans un pointeur intelligent ou un autre type qui exécute RAII (comme les conteneurs standard). Les pointeurs intelligents fournissent une sémantique de propriété des objets alloués dynamiquement. Jetez un œil à
std::unique_ptr
etstd::shared_ptr
, par exemple. Si vous les utilisez correctement, vous pouvez presque entièrement éviter d'effectuer votre propre gestion de la mémoire (voir la règle du zéro ).Pointeurs
Cependant, il existe d'autres utilisations plus générales des pointeurs bruts au-delà de l'allocation dynamique, mais la plupart ont des alternatives que vous devriez préférer. Comme précédemment, préférez toujours les alternatives, sauf si vous avez vraiment besoin de pointeurs .
Vous avez besoin d'une sémantique de référence . Parfois, vous voulez passer un objet à l'aide d'un pointeur (quelle que soit la façon dont il a été alloué) parce que vous voulez que la fonction à laquelle vous le passez ait accès à cet objet spécifique (pas à une copie). Cependant, dans la plupart des situations, vous devez préférer les types de référence aux pointeurs, car c'est précisément pour cela qu'ils sont conçus. Notez qu'il ne s'agit pas nécessairement d'étendre la durée de vie de l'objet au-delà de la portée actuelle, comme dans la situation 1 ci-dessus. Comme précédemment, si vous êtes d'accord pour transmettre une copie de l'objet, vous n'avez pas besoin de sémantique de référence.
Vous avez besoin de polymorphisme . Vous ne pouvez appeler des fonctions que de manière polymorphe (c'est-à-dire selon le type dynamique d'un objet) via un pointeur ou une référence à l'objet. Si c'est le comportement dont vous avez besoin, vous devez utiliser des pointeurs ou des références. Encore une fois, les références devraient être préférées.
Vous voulez représenter qu'un objet est facultatif en permettant
nullptr
à a d'être passé lorsque l'objet est omis. S'il s'agit d'un argument, vous devriez préférer utiliser des arguments par défaut ou des surcharges de fonctions. Sinon, vous devez de préférence utiliser un type qui encapsule ce comportement, tel questd::optional
(introduit dans C ++ 17 - avec les normes C ++ antérieures, utilisezboost::optional
).Vous souhaitez découpler les unités de compilation pour améliorer le temps de compilation . La propriété utile d'un pointeur est que vous n'avez besoin que d'une déclaration directe du type pointé (pour utiliser réellement l'objet, vous aurez besoin d'une définition). Cela vous permet de découpler des parties de votre processus de compilation, ce qui peut considérablement améliorer le temps de compilation. Voir l' idiome Pimpl .
Vous devez vous interfacer avec une bibliothèque C ou une bibliothèque de style C. À ce stade, vous êtes obligé d'utiliser des pointeurs bruts. La meilleure chose que vous puissiez faire est de ne laisser vos pointeurs bruts se détacher qu'au dernier moment possible. Vous pouvez obtenir un pointeur brut à partir d'un pointeur intelligent, par exemple, en utilisant sa
get
fonction membre. Si une bibliothèque effectue une allocation pour vous qu'elle s'attend à ce que vous désallouiez via une poignée, vous pouvez souvent envelopper la poignée dans un pointeur intelligent avec un suppresseur personnalisé qui désallouera l'objet de manière appropriée.la source
Object myObject(param1, etc...)
Il existe de nombreux cas d'utilisation pour les pointeurs.
Comportement polymorphe . Pour les types polymorphes, des pointeurs (ou références) sont utilisés pour éviter le découpage:
Sémantique de référence et éviter la copie . Pour les types non polymorphes, un pointeur (ou une référence) évitera de copier un objet potentiellement cher
Notez que C ++ 11 a une sémantique de déplacement qui peut éviter de nombreuses copies d'objets coûteux en argument de fonction et en tant que valeurs de retour. Mais l'utilisation d'un pointeur les évitera certainement et permettra plusieurs pointeurs sur le même objet (alors qu'un objet ne peut être déplacé qu'une seule fois).
Acquisition de ressources . La création d'un pointeur vers une ressource à l'aide de l'
new
opérateur est un anti-modèle en C ++ moderne. Utilisez une classe de ressources spéciale (l'un des conteneurs Standard) ou un pointeur intelligent (std::unique_ptr<>
oustd::shared_ptr<>
). Considérer:contre.
Un pointeur brut ne doit être utilisé que comme une "vue" et ne doit en aucun cas être impliqué dans la propriété, que ce soit par la création directe ou implicitement par le biais de valeurs de retour. Voir aussi ce Q&A de la FAQ C ++ .
Contrôle plus précis de la durée de vie Chaque fois qu'un pointeur partagé est copié (par exemple en tant qu'argument de fonction), la ressource vers laquelle il pointe est maintenue en vie. Les objets normaux (non créés par
new
, directement par vous ou à l'intérieur d'une classe de ressources) sont détruits lorsqu'ils sortent de la portée.la source
unique_ptr
hun(b)
nécessite également la connaissance de la signature, sauf si vous êtes d'accord pour ne pas savoir que vous avez fourni le mauvais type jusqu'à la compilation. Bien que le problème de référence ne soit généralement pas détecté au moment de la compilation et demande plus d'efforts pour le débogage, si vous vérifiez la signature pour vous assurer que les arguments sont corrects, vous pourrez également voir si l'un des arguments est une référence. de sorte que le bit de référence devient quelque chose d'un non-problème (surtout lorsque vous utilisez des IDE ou des éditeurs de texte qui affichent la signature d'une fonction sélectionnée). Aussiconst&
.Il existe de nombreuses excellentes réponses à cette question, notamment les cas d'utilisation importants des déclarations avancées, du polymorphisme, etc.
Examinons la situation en comparant les deux langues:
Java:
L'équivalent le plus proche de ceci est:
C ++:
Voyons la méthode alternative C ++:
La meilleure façon d'y penser est que - plus ou moins - Java (implicitement) gère les pointeurs vers les objets, tandis que C ++ peut gérer soit les pointeurs vers les objets, soit les objets eux-mêmes. Il existe des exceptions à cela - par exemple, si vous déclarez des types Java "primitifs", ce sont des valeurs réelles qui sont copiées, et non des pointeurs. Donc,
Java:
Cela dit, l'utilisation de pointeurs n'est PAS nécessairement la bonne ou la mauvaise façon de gérer les choses; cependant, d'autres réponses ont couvert cela de manière satisfaisante. L'idée générale est cependant qu'en C ++, vous avez beaucoup plus de contrôle sur la durée de vie des objets et sur leur emplacement.
À retenir - la
Object * object = new Object()
construction est en fait ce qui se rapproche le plus de la sémantique Java (ou C # d'ailleurs) typique.la source
Object2 is now "dead"
: Je pense que vous voulez diremyObject1
ou plus précisémentthe object pointed to by myObject1
.Object object1 = new Object(); Object object2 = new Object();
est un très mauvais code. Le deuxième nouveau ou le deuxième constructeur Object peut lancer, et maintenant object1 est divulgué. Si vous utilisez desnew
s bruts , vous devez envelopper lesnew
objets ed dans des wrappers RAII dès que possible.Une autre bonne raison d'utiliser des pointeurs serait pour les déclarations à terme . Dans un projet assez grand, ils peuvent vraiment accélérer le temps de compilation.
la source
std::unique_ptr<T>
fonctionne avec les déclarations avancées deT
. Vous avez juste besoin de vous assurer que lorsque le destructeur dustd::unique_ptr<T>
est appelé,T
c'est un type complet. Cela signifie généralement que votre classe qui contient lestd::unique_ptr<T>
déclare son destructeur dans le fichier d'en-tête et l'implémente dans le fichier cpp (même si l'implémentation est vide).Préface
Java n'a rien à voir avec C ++, contrairement au battage médiatique. La machine hype Java voudrait que vous croyiez que parce que Java a une syntaxe similaire à C ++, les langages sont similaires. Rien ne peut être plus éloigné de la vérité. Cette désinformation fait partie des raisons pour lesquelles les programmeurs Java se tournent vers C ++ et utilisent une syntaxe de type Java sans comprendre les implications de leur code.
Nous continuons
Au contraire, en fait. Le tas est beaucoup plus lent que la pile, car la pile est très simple par rapport au tas. Les variables de stockage automatique (ou variables de pile) ont leurs destructeurs appelés une fois qu'ils sont hors de portée. Par exemple:
En revanche, si vous utilisez un pointeur alloué dynamiquement, son destructeur doit être appelé manuellement.
delete
appelle ce destructeur pour vous.Cela n'a rien à voir avec le
new
syntaxe répandue en C # et Java. Ils sont utilisés à des fins complètement différentes.Avantages de l'allocation dynamique
L'un des premiers problèmes rencontrés par de nombreux programmeurs C ++ est que lorsqu'ils acceptent des entrées arbitraires d'utilisateurs, vous ne pouvez allouer qu'une taille fixe pour une variable de pile. Vous ne pouvez pas non plus modifier la taille des tableaux. Par exemple:
Bien sûr, si vous avez utilisé un
std::string
place, il sestd::string
redimensionne en interne de sorte que cela ne devrait pas être un problème. Mais essentiellement, la solution à ce problème est l'allocation dynamique. Vous pouvez allouer de la mémoire dynamique en fonction de l'entrée de l'utilisateur, par exemple:Parce que le tas est beaucoup plus grand que la pile, on peut allouer / réallouer arbitrairement autant de mémoire qu'il / elle a besoin, alors que la pile a une limitation.
En quoi est-ce un avantage que vous demandez? La réponse deviendra claire une fois que vous comprendrez la confusion / le mythe derrière les tableaux et les pointeurs. On suppose généralement qu'ils sont identiques, mais ils ne le sont pas. Ce mythe vient du fait que les pointeurs peuvent être souscrits tout comme les tableaux et à cause de la décomposition des tableaux vers les pointeurs au niveau supérieur dans une déclaration de fonction. Cependant, une fois qu'un tableau se désintègre en un pointeur, le pointeur perd son
sizeof
informations. Doncsizeof(pointer)
la taille du pointeur en octets, qui est généralement de 8 octets sur un système 64 bits.Vous ne pouvez pas attribuer à des tableaux, seulement les initialiser. Par exemple:
D'un autre côté, vous pouvez faire ce que vous voulez avec des pointeurs. Malheureusement, comme la distinction entre les pointeurs et les tableaux est ondulée à la main en Java et en C #, les débutants ne comprennent pas la différence.
Java et C # ont des fonctionnalités qui vous permettent de traiter les objets comme des autres, par exemple en utilisant le
as
mot - clé. Donc, si quelqu'un voulait traiter unEntity
objet comme unPlayer
objet, on pourrait le faire.Player player = Entity as Player;
C'est très utile si vous avez l'intention d'appeler des fonctions sur un conteneur homogène qui ne devraient s'appliquer qu'à un type spécifique. La fonctionnalité peut être obtenue de la même manière ci-dessous:Donc, si seulement Triangles avait une fonction Rotation, ce serait une erreur de compilation si vous essayiez de l'appeler sur tous les objets de la classe. À l'aide de
dynamic_cast
, vous pouvez simuler leas
mot - clé. Pour être clair, si une conversion échoue, elle renvoie un pointeur non valide. Donc!test
s'agit essentiellement d'un raccourci pour vérifier sitest
NULL ou un pointeur non valide, ce qui signifie que la conversion a échoué.Avantages des variables automatiques
Après avoir vu toutes les grandes choses que l'allocation dynamique peut faire, vous vous demandez probablement pourquoi personne n'utiliserait pas l'allocation dynamique tout le temps? Je vous ai déjà dit une raison, le tas est lent. Et si vous n'avez pas besoin de toute cette mémoire, vous ne devriez pas en abuser. Voici donc quelques inconvénients sans ordre particulier:
Il est sujet aux erreurs. L'allocation manuelle de mémoire est dangereuse et vous êtes sujet à des fuites. Si vous ne maîtrisez pas le débogueur ou
valgrind
(un outil de fuite de mémoire), vous pouvez retirer vos cheveux de votre tête. Heureusement, les idiomes RAII et les pointeurs intelligents atténuent un peu cela, mais vous devez être familier avec des pratiques telles que la règle de trois et la règle de cinq. Il y a beaucoup d'informations à prendre et les débutants qui ne savent pas ou s'en moquent tomberont dans ce piège.Ce n'est pas nécessaire. Contrairement à Java et C # où il est idiomatique d'utiliser le
new
mot - clé partout, en C ++, vous ne devez l'utiliser que si vous en avez besoin. La phrase courante va, tout ressemble à un clou si vous avez un marteau. Alors que les débutants qui commencent par C ++ ont peur des pointeurs et apprennent à utiliser les variables de pile par habitude, les programmeurs Java et C # commencent par utiliser des pointeurs sans le comprendre! Cela revient littéralement du mauvais pied. Vous devez abandonner tout ce que vous savez car la syntaxe est une chose, apprendre la langue en est une autre.Une optimisation que de nombreux compilateurs effectuent sont des éléments appelés élision et optimisation de la valeur de retour . Ces éléments peuvent éviter les copies inutiles, ce qui est utile pour les objets très volumineux, comme un vecteur contenant de nombreux éléments. Normalement, la pratique courante consiste à utiliser des pointeurs pour transférer la propriété plutôt que de copier les gros objets pour les déplacer . Cela a conduit à la création d'une sémantique de mouvement et de pointeurs intelligents .
Si vous utilisez des pointeurs, (N) RVO ne se produit PAS . Il est plus avantageux et moins sujet aux erreurs de tirer parti de (N) RVO plutôt que de renvoyer ou de passer des pointeurs si vous vous inquiétez de l'optimisation. Des fuites d'erreur peuvent se produire si l'appelant d'une fonction est responsable de l'
delete
ingénierie d'un objet alloué dynamiquement et autres. Il peut être difficile de suivre la propriété d'un objet si des pointeurs sont passés comme une patate chaude. Utilisez simplement les variables de pile car c'est plus simple et meilleur.la source
{ std::string* s = new std::string; } delete s; // destructor called
.... celadelete
ne fonctionnera sûrement pas parce que le compilateur ne saura plus ce quis
est?C ++ vous donne trois façons de passer un objet: par pointeur, par référence et par valeur. Java vous limite avec ce dernier (la seule exception concerne les types primitifs comme int, booléen, etc.). Si vous voulez utiliser C ++ non seulement comme un jouet étrange, vous feriez mieux de faire la différence entre ces trois façons.
Java prétend qu'il n'y a pas de problème comme «qui et quand devrait détruire cela?». La réponse est: le garbage collector, Great and Awful. Néanmoins, il ne peut pas fournir une protection à 100% contre les fuites de mémoire (oui, java peut fuir la mémoire ). En fait, GC vous donne un faux sentiment de sécurité. Plus votre SUV est gros, plus votre chemin vers l'évacuateur est long.
C ++ vous laisse face à face avec la gestion du cycle de vie des objets. Eh bien, il existe des moyens de gérer cela ( famille de pointeurs intelligents , QObject dans Qt et ainsi de suite), mais aucun d'entre eux ne peut être utilisé de manière `` tirer et oublier '' comme GC: vous devriez toujours garder à l'esprit la gestion de la mémoire. Non seulement vous devez vous soucier de détruire un objet, mais vous devez également éviter de détruire le même objet plus d'une fois.
Pas encore peur? Ok: références cycliques - manipulez-les vous-même, humain. Et rappelez-vous: tuez chaque objet précisément une fois, nous, les runtimes en C ++, n'aimons pas ceux qui jouent avec les cadavres, laissons les morts seuls.
Revenons donc à votre question.
Lorsque vous passez votre objet par valeur, pas par pointeur ou par référence, vous copiez l'objet (l'objet entier, que ce soit quelques octets ou un énorme vidage de base de données - vous êtes assez intelligent pour prendre soin d'éviter ce dernier, aren ' t vous?) chaque fois que vous faites '='. Et pour accéder aux membres de l'objet, vous utilisez '.' (point).
Lorsque vous passez votre objet par pointeur, vous copiez seulement quelques octets (4 sur les systèmes 32 bits, 8 sur les systèmes 64 bits), à savoir - l'adresse de cet objet. Et pour le montrer à tout le monde, vous utilisez cet opérateur fantaisie '->' lorsque vous accédez aux membres. Ou vous pouvez utiliser la combinaison de '*' et '.'.
Lorsque vous utilisez des références, vous obtenez le pointeur qui prétend être une valeur. C'est un pointeur, mais vous accédez aux membres via '.'.
Et, pour vous épater encore une fois: lorsque vous déclarez plusieurs variables séparées par des virgules, alors (faites attention aux aiguilles):
Exemple:
la source
std::auto_ptr
est obsolète, veuillez ne pas l'utiliser.En C ++, les objets alloués sur la pile (en utilisant une
Object object;
instruction dans un bloc) ne vivront que dans la portée dans laquelle ils sont déclarés. Lorsque le bloc de code termine son exécution, l'objet déclaré est détruit. Alors que si vous allouez de la mémoire sur le tas, en utilisantObject* obj = new Object()
, ils continuent à vivre dans le tas jusqu'à ce que vous appeliezdelete obj
.Je créerais un objet sur le tas quand j'aimerais utiliser l'objet non seulement dans le bloc de code qui l'a déclaré / alloué.
la source
Object obj
n'est pas toujours sur la pile - par exemple des globaux ou des variables membres.Je vais comparer comment cela fonctionne à l'intérieur du corps de la fonction si vous utilisez:
À l'intérieur de la fonction, votre
myObject
sera détruit une fois cette fonction revenue. C'est donc utile si vous n'avez pas besoin de votre objet en dehors de votre fonction. Cet objet sera placé sur la pile de threads actuelle.Si vous écrivez à l'intérieur du corps de la fonction:
alors l'instance de classe Object pointée par
myObject
ne sera pas détruite une fois la fonction terminée, et l'allocation est sur le tas.Maintenant, si vous êtes programmeur Java, le deuxième exemple est plus proche de la façon dont l'allocation d'objets fonctionne sous java. Cette ligne:
Object *myObject = new Object;
équivaut à java:Object myObject = new Object();
. La différence est que sous java myObject récupérera les ordures, tandis que sous c ++ il ne sera pas libéré, vous devez quelque part appeler explicitement `delete myObject; ' sinon vous introduirez des fuites de mémoire.Depuis c ++ 11, vous pouvez utiliser des moyens sûrs d'allocations dynamiques:,
new Object
en stockant les valeurs dans shared_ptr / unique_ptr.De plus, les objets sont très souvent stockés dans des conteneurs, comme map-s ou vector-s, ils géreront automatiquement la durée de vie de vos objets.
la source
then myObject will not get destroyed once function ends
Ce sera absolument le cas.myObject
sera toujours détruit, comme n'importe quelle autre variable locale. La différence est que sa valeur est un pointeur vers un objet, pas l'objet lui-même, et la destruction d'un pointeur stupide n'affecte pas sa pointe. Ainsi, l' objet survivra à cette destruction.Techniquement, c'est un problème d'allocation de mémoire, mais voici deux autres aspects pratiques de cela. Cela a à voir avec deux choses: 1) Portée, lorsque vous définissez un objet sans pointeur, vous ne pourrez plus y accéder après le bloc de code dans lequel il est défini, tandis que si vous définissez un pointeur avec "nouveau", vous pouvez y accéder de n'importe où vous avez un pointeur vers cette mémoire jusqu'à ce que vous appeliez "supprimer" sur le même pointeur. 2) Si vous voulez passer des arguments à une fonction, vous voulez passer un pointeur ou une référence pour être plus efficace. Lorsque vous passez un objet, l'objet est copié, s'il s'agit d'un objet qui utilise beaucoup de mémoire, cela peut consommer du processeur (par exemple, vous copiez un vecteur plein de données). Lorsque vous passez un pointeur, tout ce que vous passez est un entier (selon l'implémentation mais la plupart d'entre eux sont un entier).
Autre que cela, vous devez comprendre que "nouveau" alloue de la mémoire sur le tas qui doit être libéré à un moment donné. Lorsque vous n'avez pas à utiliser "nouveau", je vous suggère d'utiliser une définition d'objet régulière "sur la pile".
la source
Eh bien, la question principale est pourquoi devrais-je utiliser un pointeur plutôt que l'objet lui-même? Et ma réponse, vous ne devriez (presque) jamais utiliser de pointeur au lieu d'objet, car C ++ a des références , il est plus sûr que les pointeurs et garantit les mêmes performances que les pointeurs.
Une autre chose que vous avez mentionnée dans votre question:
Comment ça marche? Il crée un pointeur de
Object
type, alloue de la mémoire pour s'adapter à un objet et appelle le constructeur par défaut, sonne bien, non? Mais en réalité, ce n'est pas si bon, si vous avez alloué dynamiquement de la mémoire (mot-clé utilisénew
), vous devez également libérer de la mémoire manuellement, cela signifie que dans le code, vous devriez avoir:Cela appelle destructeur et libère de la mémoire, semble facile, mais dans les grands projets, il peut être difficile de détecter si une mémoire libérée par un thread ou non, mais à cette fin, vous pouvez essayer des pointeurs partagés , cela diminue légèrement les performances, mais il est beaucoup plus facile de travailler avec leur.
Et maintenant, une introduction est terminée et reviens à la question.
Vous pouvez utiliser des pointeurs au lieu d'objets pour obtenir de meilleures performances lors du transfert de données entre les fonctions.
Jetez un oeil, vous avez
std::string
(c'est aussi un objet) et il contient vraiment beaucoup de données, par exemple du gros XML, maintenant vous devez les analyser, mais pour cela vous avez une fonctionvoid foo(...)
qui peut être déclarée de différentes manières:void foo(std::string xml);
Dans ce cas, vous copiez toutes les données de votre variable dans la pile de fonctions, cela prend un certain temps, donc vos performances seront faibles.void foo(std::string* xml);
Dans ce cas, vous passerez le pointeur à l'objet, à la même vitesse que lasize_t
variable de passage , mais cette déclaration est sujette aux erreurs, car vous pouvez passer unNULL
pointeur ou un pointeur non valide. Pointeurs généralement utilisés dansC
car il n'a pas de références.void foo(std::string& xml);
Ici, vous passez une référence, fondamentalement, c'est la même chose que de passer un pointeur, mais le compilateur fait certaines choses et vous ne pouvez pas passer une référence non valide (en fait, il est possible de créer une situation avec une référence non valide, mais il trompe le compilateur).void foo(const std::string* xml);
Voici la même chose que la seconde, seule la valeur du pointeur ne peut pas être modifiée.void foo(const std::string& xml);
Voici la même chose que la troisième, mais la valeur de l'objet ne peut pas être modifiée.Ce que je veux dire de plus, vous pouvez utiliser ces 5 façons de transmettre des données, quelle que soit la méthode d'allocation que vous avez choisie (avec
new
ou régulière ).Une autre chose à mentionner, lorsque vous créez un objet de manière régulière , vous allouez de la mémoire dans la pile, mais pendant que vous le créez avec
new
vous allouez un tas. Il est beaucoup plus rapide d'allouer la pile, mais c'est un peu petit pour les très grands tableaux de données, donc si vous avez besoin d'un gros objet, vous devez utiliser le tas, car vous pouvez obtenir un débordement de pile, mais généralement ce problème est résolu en utilisant des conteneurs STL et n'oubliez passtd::string
est également un conteneur, certains gars l'ont oublié :)la source
Disons que vous en avez
class A
qui contiennentclass B
Lorsque vous voulez appeler une fonction de l'class B
extérieur,class A
vous obtiendrez simplement un pointeur vers cette classe et vous pourrez faire ce que vous voulez et cela changera également le contexte declass B
votreclass A
Mais soyez prudent avec un objet dynamique
la source
Il existe de nombreux avantages à utiliser des pointeurs pour s'opposer -
la source
Cela a été longuement discuté, mais en Java, tout est un pointeur. Il ne fait aucune distinction entre les allocations de pile et de tas (tous les objets sont alloués sur le tas), donc vous ne réalisez pas que vous utilisez des pointeurs. En C ++, vous pouvez mélanger les deux, selon vos besoins en mémoire. Les performances et l'utilisation de la mémoire sont plus déterministes en C ++ (duh).
la source
Cela créera une référence à un objet (sur le tas) qui doit être supprimé explicitement pour éviter une fuite de mémoire .
Cela créera un objet (myObject) de type automatique (sur la pile) qui sera automatiquement supprimé lorsque l'objet (myObject) sortira du cadre.
la source
Un pointeur fait directement référence à l'emplacement mémoire d'un objet. Java n'a rien de tel. Java a des références qui font référence à l'emplacement de l'objet via des tables de hachage. Vous ne pouvez rien faire comme l'arithmétique des pointeurs en Java avec ces références.
Pour répondre à votre question, c'est juste votre préférence. Je préfère utiliser la syntaxe de type Java.
la source
Avec des pointeurs ,
peut parler directement à la mémoire.
peut empêcher beaucoup de fuites de mémoire d'un programme en manipulant des pointeurs.
la source
Une des raisons d'utiliser des pointeurs est de s'interfacer avec les fonctions C. Une autre raison est d'économiser de la mémoire; par exemple: au lieu de passer un objet qui contient beaucoup de données et qui a un constructeur de copie gourmand en processeur à une fonction, passez simplement un pointeur sur l'objet, économisant de la mémoire et de la vitesse surtout si vous êtes dans une boucle, cependant un référence serait mieux dans ce cas, sauf si vous utilisez un tableau de style C.
la source
Dans les zones où l'utilisation de la mémoire est à son maximum, les pointeurs sont pratiques. Par exemple, considérons un algorithme minimax, où des milliers de nœuds seront générés à l'aide d'une routine récursive, et les utiliser plus tard pour évaluer le prochain meilleur mouvement du jeu, la capacité de désallouer ou de réinitialiser (comme dans les pointeurs intelligents) réduit considérablement la consommation de mémoire. Alors que la variable non pointeur continue d'occuper de l'espace jusqu'à ce que son appel récursif renvoie une valeur.
la source
Je vais inclure un cas d'utilisation important du pointeur. Lorsque vous stockez un objet dans la classe de base, mais il peut être polymorphe.
Donc, dans ce cas, vous ne pouvez pas déclarer bObj en tant qu'objet direct, vous devez avoir un pointeur.
la source
"La nécessité est la mère de l'invention." La différence la plus importante que je voudrais souligner est le résultat de ma propre expérience de codage. Parfois, vous devez passer des objets à des fonctions. Dans ce cas, si votre objet est d'une très grande classe, le passer en tant qu'objet copiera son état (ce que vous ne voudrez peut-être pas .. ET PEUT ÊTRE GRAND AU-DESSUS), ce qui entraîne un surcoût de copie de l'objet. Tandis que le pointeur est fixé Taille de 4 octets (en supposant 32 bits). D'autres raisons sont déjà mentionnées ci-dessus ...
la source
std::string test;
nous avons,void func(const std::string &) {}
mais à moins que la fonction ne doive changer l'entrée, auquel cas je recommande d'utiliser des pointeurs (pour que toute personne lisant le code le remarque&
et comprenne que la fonction peut changer son entrée)Il existe déjà de nombreuses excellentes réponses, mais permettez-moi de vous donner un exemple:
J'ai une classe d'objets simple:
Je fais un vecteur pour en tenir un tas.
std::vector<Item> inventory;
Je crée un million d'objets Item et les repousse sur le vecteur. Je trie le vecteur par nom, puis je fais une recherche binaire itérative simple pour un nom d'élément particulier. Je teste le programme et cela prend plus de 8 minutes pour terminer son exécution. Ensuite, je change mon vecteur d'inventaire comme suit:
std::vector<Item *> inventory;
... et créer mes millions d'objets d'objets via new. Les seules modifications que j'apporte à mon code consistent à utiliser les pointeurs vers les éléments, à l'exception d'une boucle que j'ajoute pour le nettoyage de la mémoire à la fin. Ce programme s'exécute en moins de 40 secondes, ou mieux qu'une augmentation de vitesse de 10x. EDIT: Le code est sur http://pastebin.com/DK24SPeW Avec les optimisations du compilateur, il ne montre qu'une augmentation de 3,4x sur la machine sur laquelle je viens de le tester, ce qui est encore considérable.
la source
push_back
. Bien sûr, cela copie. Vous devriez avoir étéemplace
en place lors de la création de vos objets (sauf si vous avez besoin qu'ils soient mis en cache ailleurs).