Pourquoi devrais-je utiliser un pointeur plutôt que l'objet lui-même?

je viens d'un fond Java et j'ai commencé à travailler avec des objets en C++. Mais une chose qui m'est venue 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 vois pas pourquoi on devrait faire ça. Je suppose que ça a à voir avec l'efficacité et la vitesse puisque nous avons un accès direct à l'adresse mémoire. Suis-je le droit?

1362
demandé sur Peter Mortensen 2014-03-03 15:54:16
la source

22 ответов

il est très regrettable que vous voyiez si souvent la répartition dynamique. Cela montre juste combien il y a de mauvais programmeurs C++.

Dans un sens, vous avez deux questions enfermé dans une. La première est de savoir quand utiliser l'allocation dynamique (en utilisant new )? La seconde, c'est quand utiliser des pointeurs?

l'important message à emporter est que vous devez toujours utiliser l'outil approprié pour le travail . Dans dans presque toutes les situations, il y a quelque chose de plus approprié et plus sûr que d'effectuer une répartition dynamique manuelle et/ou d'utiliser des pointeurs bruts.

allocation dynamique

dans votre question, vous avez démontré deux façons de créer un objet. La principale différence est la durée de conservation de l'objet. En faisant Object myObject; dans un bloc, l'objet est créé avec la durée de stockage automatique, ce qui signifie qu'il sera détruit automatiquement quand il est hors de portée. Lorsque vous faites new Object() , l'objet a une durée de stockage dynamique, ce qui signifie qu'il reste vivant jusqu'à ce que vous explicitement delete . Vous ne devez utiliser la durée de stockage dynamique que lorsque vous en avez besoin. C'est-à-dire, vous devriez toujours préférez créer des objets avec la durée de stockage automatique quand vous pouvez .

les deux principales situations dans lesquelles vous pourriez avoir besoin d'une allocation dynamique:

  1. Vous avez besoin de l'objet à survivre à la portée actuelle - cet objet spécifique à l'emplacement mémoire spécifique, pas une copie. Si vous êtes d'accord pour copier/déplacer l'objet (la plupart du temps vous devriez être), vous devriez préférer un objet automatique.
  2. Vous avez besoin d'allouer beaucoup de mémoire , qui peut facilement remplir la pile. Ce serait bien si nous n'avions pas à nous préoccuper de ce (la plupart du temps vous ne devriez pas avoir à le faire), car c'est vraiment en dehors du domaine du C++, mais malheureusement nous devons faire face à la réalité des systèmes pour lesquels nous développons.

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 la sémantique de propriété des objets alloués dynamiquement. Regardez std::unique_ptr et std::shared_ptr , par exemple. Si vous les utilisez correctement, vous pouvez presque entièrement éviter d'effectuer votre propre gestion de mémoire (voir la Règle du zéro ).

pointeurs

cependant, il existe d'autres utilisations plus générales pour les pointeurs bruts au-delà de l'allocation dynamique, mais la plupart ont des alternatives que vous devriez préférer. Comme avant, préfèrent toujours les alternatives sauf si vous avez vraiment besoin de pointeurs .

  1. vous avez besoin de la sémantique de référence . Parfois vous voulez passer un objet en utilisant un pointeur (peu importe comment il a été attribué) parce que vous voulez que la fonction à laquelle vous le Passez ait accès à cet objet spécifique (pas une copie de celui-ci). Cependant, dans la plupart des situations, vous devriez préférer les types de référence aux pointeurs, parce que c'est précisément ce qu'ils sont conçus pour. Il ne s'agit pas nécessairement de prolonger la durée de vie de l'objet au-delà de la portée actuelle, comme dans la situation 1 ci-dessus. Comme avant, si vous êtes d'accord pour passer une copie de l'objet, vous n'avez pas besoin de sémantique de référence.

  2. Vous avez besoin de polymorphisme . Vous ne pouvez appeler les fonctions polymorphiquement (c'est-à-dire selon le type dynamique d'un objet) qu'à travers un pointeur ou une référence à l'objet. Si c'est le comportement dont vous avez besoin, puis vous devez utiliser des pointeurs ou des références. Encore une fois, les références devraient être préférées.

  3. vous voulez indiquer qu'un objet est facultatif en permettant qu'un nullptr soit passé lorsque l'objet est omis. Si c'est un argument, vous devriez préférer utiliser des arguments par défaut ou des surcharges de fonctions. Sinon, vous devriez préférer utiliser un type qui encapsule ce comportement, tel que std::optional (introduit en C++17 - avec les anciennes normes c++, utilisez boost::optional ).

  4. vous voulez 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 forward du type pointed-to (pour réellement utiliser 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 améliorer considérablement le temps de compilation. Voir le Pimpl idiome .

  5. Vous avez besoin d'une interface avec un C library ou un C-bibliothèque de styles. À ce stade, vous êtes obligé d'utiliser des pointeurs. La meilleure chose que vous pouvez faire est de vous assurer que vous laissez vos premières pointeurs lâche au dernier moment possible. Vous pouvez obtenir un pointeur brut à partir d'un pointeur intelligent, par exemple, en utilisant sa fonction de membre get . Si une bibliothèque effectue une affectation pour vous qu'il s'attend à ce que vous deallocate via une poignée, vous pouvez souvent envelopper la poignée vers le haut dans un pointeur intelligent avec un deleter personnalisé qui désallocera l'objet de manière appropriée.

1371
répondu Joseph Mansfield 2017-01-13 14:52:08
la source

il existe de nombreux cas d'utilisation pour les pointeurs.

le comportement Polymorphique . Pour les types polymorphes, des pointeurs (ou des références) sont utilisés pour éviter le tranchage:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

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 coûteux

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

notez que C++11 a la sémantique des mouvements cela peut éviter de nombreuses copies d'objets coûteux dans l'argument de fonction et comme valeurs de retour. Mais l'utilisation d'un pointeur évitera certainement ceux-ci et permettra plusieurs pointeurs sur le même objet (alors qu'un objet ne peut être déplacé qu'à partir d'une seule fois).

l'acquisition de Ressources . Créer un pointeur vers une ressource en utilisant l'opérateur new est un anti-pattern en C++moderne. Utilisez une classe de ressource spéciale (l'une des 1519120920 "( std::unique_ptr<> ou std::shared_ptr<> ). Prendre en considération:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

vs.

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

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 des valeurs de retour. Voir aussi cette Q&R de la FAQ C++ .

plus fine-grain de vie-temps de contrôle Chaque fois qu'un pointeur partagé est en cours de copie (par exemple, comme un argument de fonction) la ressource, il est maintenu en vie. Les objets réguliers (qui ne sont pas créés par new , soit directement par vous, soit dans une classe ressource) sont détruits lorsqu'ils sortent de la portée.

158
répondu TemplateRex 2017-05-23 15:34:53
la source

il y a beaucoup d'excellentes réponses à cette question, y compris les cas importants d'utilisation de déclarations forward, polymorphisme, etc. mais je sens qu'une partie de "l'âme" de votre question n'est pas répondue - à savoir ce que les syntaxes différentes signifient à travers Java et C++.

examinons la situation en comparant les deux langues:

Java:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

l'équivalent Le plus proche, est:

c++:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

voyons l'alternative c++ way:

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

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 y a 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:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

cela dit, utiliser des pointeurs n'est pas nécessairement la bonne ou la mauvaise façon de gérer les choses; toutefois, d'autres réponses ont couvert cela de façon satisfaisante. L'idée générale est que, en C++, vous avez beaucoup plus de contrôle sur la durée de vie des objets, et l'endroit où ils vivent.

Take home point -- la construction Object * object = new Object() est en fait ce qui est le plus proche de la sémantique Java (ou C# pour cette matière) typique.

115
répondu Gerasimos R 2017-01-20 14:10:57
la source

une Autre bonne raison d'utiliser des pointeurs serait pour déclaration . Dans un projet assez grand, ils peuvent vraiment accélérer le temps de compilation.

73
répondu Burnt Toast 2014-03-05 09:38:46
la source

Préface

Java n'est rien comme C++, contrairement à l'exagération. La machine Java hype voudrait que vous croyiez que parce que Java a la syntaxe C++ comme syntaxe, que les langues sont similaires. Rien ne peut être plus éloigné de la vérité. Cette information erronée explique en partie pourquoi les programmeurs Java vont vers C++ et utilisent la syntaxe Java sans comprendre les implications de leur code.

à Partir nous allons

But Je ne peux pas comprendre pourquoi devrions-nous faire de cette façon. Je suppose qu'il s' il s'agit de l'efficacité et de la rapidité puisque nous avons un accès direct à adresse de la mémoire. Suis-je le droit?

au contraire, en fait. le tas est beaucoup plus lent que la pile, parce que la pile est très simple par rapport au tas. Les variables de stockage automatiques (ou variables de pile) ont leurs destructeurs appelés une fois qu'elles sont hors de portée. Par exemple:

{
    std::string s;
}
// s is destroyed here

par contre, si vous utilisez un pointeur attribué dynamiquement, son destructeur doit être appelé manuellement. delete appelle ce destructeur pour vous.

{
    std::string* s = new std::string;
}
delete s; // destructor called

cela n'a rien à voir avec la syntaxe new prévalant en C# et Java. Ils sont utilisés à des fins complètement différentes.

avantages de l'allocation dynamique

1. Vous n'avez pas à connaître le Taille du tableau à l'avance

l'un des premiers problèmes que rencontrent de nombreux programmeurs C++ est que lorsqu'ils acceptent des entrées arbitraires de la part des utilisateurs, vous ne pouvez attribuer qu'une taille fixe à une variable de pile. Vous ne pouvez pas modifier la taille des tableaux. Par exemple:

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

bien sûr, si vous avez utilisé un std::string à la place, std::string se redimensionne à l'interne de sorte que ce ne devrait pas être un problème. Mais, pour l'essentiel la solution à ce problème est l'allocation dynamique. Vous pouvez allouer la mémoire dynamique basée sur l'entrée de l'utilisateur, par exemple:

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

note : Une erreur que beaucoup de débutants font l'utilisation de l' tableaux de longueur variable. C'est une extension GNU et aussi une en Clang parce qu'ils reflètent de nombreuses extensions de GCC. Donc la suite Il ne faut pas se fier à int arr[n] .

parce que le tas est beaucoup plus grand que la pile, on peut arbitrairement allouer/réallouer autant de mémoire qu'il/elle a besoin, alors que la pile a une limitation.

2. Les tableaux ne sont pas des pointeurs

en quoi est-ce un avantage que vous demandez? La réponse deviendra claire une fois que vous comprendrez la confusion/mythe derrière les tableaux et les pointeurs. Il est communément admis que ce sont les mêmes, 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 aux pointeurs au niveau supérieur dans une déclaration de fonction. Cependant, une fois qu'un tableau se décompose en pointeur, le pointeur perd son information sizeof . Ainsi sizeof(pointer) donnera la taille du pointeur en octets, qui est généralement de 8 octets sur un système de 64 bits.

vous ne pouvez pas affecter aux tableaux, seulement les initialiser. Par exemple:

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

, d'autre part, vous pouvez faire ce que vous voulez avec des pointeurs. Malheureusement, parce que la distinction entre les pointeurs et les tableaux est agitée à la main en Java et C#, les débutants ne comprennent pas la différence.

3. Polymorphisme

Java et C# ont des installations qui vous permettent de traiter les objets comme un autre, par exemple en utilisant le mot-clé as . Donc, si quelqu'un voulait traiter un objet Entity comme un objet Player , on pourrait 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 devrait s'appliquer qu'à un type spécifique. La fonctionnalité peut être réalisée de la même manière ci-dessous:

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

donc si seulement les Triangles avaient une fonction de rotation, ce serait une erreur de compilateur si vous essayiez de l'appeler sur tous les objets de la classe. En utilisant dynamic_cast , vous pouvez simuler as mot clé. Pour être clair, si un plâtre échoue, il renvoie un pointeur invalide. Donc !test est essentiellement un raccourci pour vérifier si test est nul ou un pointeur invalide, ce qui signifie que la fonte 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 l'allocation dynamique tout le temps? Je vous ai déjà dit une raison, le tas est lente. Et si tu n'as pas besoin de toute cette mémoire, tu ne devrais pas en abuser. Donc voici quelques inconvénients dans aucun ordre particulier:

  • C'est sujette à erreur. L'attribution manuelle de la mémoire est dangereuse et vous êtes sujet à des fuites. Si vous n'êtes pas compétent pour utiliser le débogueur ou valgrind (un outil de fuite de mémoire), vous pouvez tirer vos cheveux de votre tête. Heureusement, les expressions idiomatiques RAII et les astuces intelligentes atténuent un peu cela, mais vous devez être familier avec les pratiques comme la règle des trois et la règle des cinq. C'est beaucoup d'informations à prendre en compte, et les débutants qui ne savent pas ou ne s'en soucient pas tomberont dans ce piège.

  • Il n'est pas nécessaire. Contrairement à Java et C# où il est idiomatique d'utiliser le mot-clé new partout, en C++, vous ne devriez l'utiliser que si vous en avez besoin. On dit souvent que tout ressemble à un clou si on a un marteau. Alors que les débutants qui commencent avec C++ ont peur de pointeurs et apprendre à utiliser des variables de pile par habitude, Java et c # programmeurs démarrer en utilisant des pointeurs sans le comprendre! C'est littéralement marcher du mauvais pied. Vous devez abandonner tout ce que vous savez parce que la syntaxe est une chose, apprendre la langue en est une autre.

1. (N)RVO - Aka, (Nommé) de la Valeur de Retour d'Optimisation

une optimisation de nombreux compilateurs font sont des choses appelées elision et optimisation de la valeur de retour . Ces choses peuvent éviter les copys inutiles qui est utile pour les objets qui sont très grands, comme un vecteur contenant de nombreux éléments. Normalement, la pratique courante consiste à utiliser des pointeurs vers transférer la propriété plutôt que de copier les grands objets vers déplacer les contourner. Ceci a conduit à la création de mouvement sémantique et pointeurs intelligents .

si vous utilisez des pointeurs, (N)RVO ne pas se produire. Il est plus avantageux et moins enclin à l'erreur de profiter de (N)RVO plutôt que de retourner ou passer des pointeurs si vous êtes inquiet de l'optimisation. Des fuites d'erreur peuvent se produire si l'appelant d'une fonction est responsable de delete ing un objet alloué dynamiquement et ainsi de suite. Il peut être difficile de suivre la possession d'un objet si les pointeurs sont passés comme une patate chaude. Il suffit d'utiliser les variables stack parce que c'est plus simple et mieux.

62
répondu user3391320 2018-06-26 19:28:47
la source

C++ vous donne trois façons de passer d'un objet: par pointeur, par référence et par valeur. Java vous limite avec le dernier (la seule exception est les types primitifs comme int, booléen etc). Si vous voulez utiliser C++ pas juste comme un jouet bizarre, alors vous feriez mieux de connaître la différence entre ces trois façons.

Java prétend qu'il n'y a pas de problème tel que " qui et quand devrait le détruire?'. La réponse est: le collecteur D'ordures, grand et terrible. Néanmoins, il ne peut pas fournir une protection de 100% contre les fuites de mémoire (Oui, java can fuite de mémoire ). En fait, GC vous donne un faux sentiment de sécurité. Plus votre SUV est grand, plus vous vous dirigez vers l'évacuateur.

C++ vous laisse face à face avec la gestion du cycle de vie de l'objet. Eh bien, il y a des moyens pour faire face à cela ( pointeurs intelligents famille, QObject dans Qt et ainsi de suite), mais aucun d'eux ne peut être utilisé dans" feu et oubliez la manière comme GC: vous devriez toujours garder à l'esprit la manipulation de la mémoire. Non seulement devez-vous vous souciez de détruire un objet, vous devez également éviter de détruire l'objet même plus d'une fois.

pas encore peur? OK: références cycliques-manipulez-les vous-même, humain. Et rappelez-vous: tuer chaque objet précisément une fois, nous les runtimes C++ n'aimons pas ceux qui se frottent aux cadavres, laissez les morts tranquilles.

donc, revenons à votre question.

quand vous passez votre objet par valeur, pas par pointeur ou par référence, vous copiez l'objet (l'objet entier, que ce soit un couple d'octets ou un énorme dump de base de données - vous êtes assez intelligent pour prendre soin d'éviter ce dernier, n'est-ce pas?) chaque fois que vous faites '='. Et pour accéder aux membres de l'objet, vous utilisez '.' (dot.)

Lorsque vous passez votre objet par pointeur, vous copiez juste quelques octets (4 sur les systèmes 32 bits, 8 64-bit), à savoir: - l'adresse de ce objet. Et pour montrer cela à tout le monde, vous utilisez cet opérateur de fantaisie '->' lorsque vous accédez aux membres. Ou vous pouvez utiliser la combinaison de '*' et '.'.

Lorsque vous utilisez des références, vous obtenez alors le pointeur qui prétend être une valeur. C'est un pointeur, mais vous accédez aux membres par".'.

et, pour vous souffler l'esprit encore une fois: quand vous déclarez plusieurs variables séparées par des virgules, alors (regardez les mains):

  • Le Type est donné à tout le monde
  • valeur / indicateur / modificateur de référence est individuel

exemple:

struct MyStruct
{
    int* someIntPointer, someInt; //here comes the surprise
    MyStruct *somePointer;
    MyStruct &someReference;
};

MyStruct s1; //we allocated an object on stack, not in heap

s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'

s1.someReference.someInt = 5; //now s1.someInt has value '5'
                              //although someReference is not value, it's members are accessed through '.'

MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.

//OK, assume we have '=' defined in MyStruct

s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one
21
répondu Kirill Gamazkov 2014-04-17 19:55:16
la source

en C++, les objets alloués sur la pile (en utilisant Object object; dans un bloc) ne vivront que dans la portée dans laquelle ils sont déclarés. Lorsque le bloc de code termine l'exécution, l'objet déclaré est détruit. Alors que si vous attribuez la mémoire sur le tas, en utilisant Object* obj = new Object() , ils continuent à vivre dans le tas jusqu'à ce que vous appeliez delete obj .

je créerais un objet sur tas quand je voudrais utiliser l'objet non seulement dans le bloc de code qui a déclaré / alloué il.

19
répondu Karthik Kalyanasundaram 2014-03-04 13:48:26
la source

mais je ne comprends pas pourquoi on l'utilise comme ça?

je vais comparer comment il fonctionne à l'intérieur du corps de fonction si vous utilisez:

Object myObject;

à l'intérieur de la fonction, votre myObject sera détruit une fois que cette fonction retournera. Ceci est donc utile si vous n'avez pas besoin de votre objet en dehors de votre fonction. Cet objet sera mis sur la pile de thread courant.

Si vous écrivez à l'intérieur corps de la fonction:

 Object *myObject = new Object;

alors l'instance de classe D'objet 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, alors le deuxième exemple est plus proche de la façon dont l'allocation d'objet fonctionne sous java. Cette ligne: Object *myObject = new Object; est l'équivalent de java: Object myObject = new Object(); . La différence est que sous java myObject sera collecté les ordures, tandis que sous c++ il ne sera pas libéré, vous devez quelque part explicitement appeler `supprimer 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 des valeurs dans shared_ptr/unique_ptr.

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

en outre, les objets sont très souvent stockés dans des conteneurs, comme map-s ou vector-s, ils géreront automatiquement une durée de vie de vos objets.

19
répondu marcinj 2018-06-26 16:49:25
la source

techniquement, il s'agit d'une question d'attribution de mémoire, mais voici deux autres aspects pratiques de cette. Ça a à voir avec deux choses.: 1) Scope, quand 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, alors que si vous définissez un pointeur avec "nouveau" alors 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 que vous souhaitez passer un pointeur ou une référence afin d'être plus efficace. Lorsque vous passez un objet alors l'objet est copié, s'il s'agit d'un objet qui utilise beaucoup de mémoire, cela peut être une consommation CPU (par exemple vous copiez un vecteur plein de données). Lorsque vous passez un pointeur tout ce que vous passez est un int (selon l'implémentation mais la plupart d'entre eux sont un int).

mis à part que 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 à utilisez " nouveau "je vous suggère d'utiliser une définition d'objet régulier"sur la pile".

12
répondu in need of help 2014-03-03 16:05:54
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 à la place de l'objet, parce que C++ a références , il est plus sûr que les pointeurs et garantit la même performance que les pointeurs.

autre chose que vous avez mentionnée dans votre question:

Object *myObject = new Object;

comment ça marche? Il crée pointeur de Object type, alloue de la mémoire pour s'adapter à un objet et les appels de constructeur par défaut, ça sonne bien, non? Mais en fait, ce n'est pas si bon, si vous avez affecté dynamiquement de la mémoire (mot-clé utilisé new ), vous devez aussi libérer la mémoire manuellement, ce qui signifie en code que vous devriez avoir:

delete myObject;

cela appelle destructeur et libère la mémoire, semble facile, mais dans les grands projets peuvent être difficiles à détecter si un fil a libéré la mémoire ou non, mais à cette fin, vous pouvez essayer partagé pointeurs , ces diminue légèrement les performances, mais il est beaucoup plus facile de travailler avec eux.


et maintenant une certaine introduction est terminée et revenir à la question.

vous pouvez utiliser des pointeurs à la place des objets pour obtenir de meilleures performances tout en transférant des données entre les fonctions.

regardez, vous avez std::string (il est aussi objet) et il contient vraiment beaucoup de données, pour exemple grand XML, maintenant vous devez l'analyser, mais pour cela vous avez la fonction void foo(...) qui peut être déclaré de différentes façons:

  1. void foo(std::string xml); Dans ce cas, vous copierez toutes les données de votre variable à la pile de fonctions, cela prend un certain temps, de sorte que votre performance sera faible.
  2. void foo(std::string* xml); Dans ce cas, vous passerez pointeur à l'objet, même vitesse que passer size_t variable, mais cette déclaration a tendance à l'erreur, parce que vous pouvez passer le pointeur NULL ou le pointeur invalide. Pointeurs généralement utilisés dans C parce qu'il n'a pas de références.
  3. void foo(std::string& xml); Ici vous passez la référence, fondamentalement c'est la même chose que passer le pointeur, mais le compilateur fait quelques trucs et vous ne pouvez pas passer la référence invalide (en fait il est possible de créer la situation avec la référence invalide, mais c'est le compilateur trompeur).
  4. void foo(const std::string* xml); Voici la même chose que la deuxième, juste pointer la valeur ne peut pas être modifié.
  5. 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 changée.

ce que je veux mentionner de plus, vous pouvez utiliser ces 5 façons de transmettre des données, peu importe la méthode d'allocation que vous avez choisie (avec new ou régulier ).


autre chose à mentionner, lorsque vous créez un objet dans voie régulière , vous attribuez la mémoire dans la pile, mais pendant que vous la Créez avec new vous attribuez tas. Il est beaucoup plus rapide d'allouer la pile, mais c'est une sorte un petit pour vraiment grands tableaux de données, donc si vous avez besoin d'un gros objet, vous devriez utiliser tas, parce que vous pouvez obtenir le débordement de la pile, mais habituellement ce problème est résolu en utilisant conteneurs STL et se rappeler std::string est aussi conteneur, certains gars l'ont oublié:)

6
répondu ST3 2014-03-08 14:48:02
la source

disons que vous avez class A qui contiennent class B quand vous voulez appeler une fonction de class B en dehors de class A vous obtiendrez simplement un pointeur vers cette classe et vous pourrez faire ce que vous voulez et cela changera aussi le contexte de class B dans votre class A

mais attention à l'objet dynamique

5
répondu Quest 2014-03-03 16:02:35
la source

il y a de nombreux avantages à utiliser des pointeurs pour objecter -

  1. efficacité (comme vous l'avez déjà souligné). Les objets de passage à fonctions de création de nouvelles copies de l'objet.
  2. fonctionne avec des objets provenant de bibliothèques tierces. Si votre objet appartient à un code tiers et les auteurs prévoient l'utilisation de leurs objets à travers des pointeurs seulement (aucun constructeur de copie etc) la seule façon que vous pouvez passer autour de cette objet utilise des pointeurs. Passage par valeur peut causer des problèmes. (Profond copie / copie).
  3. si l'objet possède une ressource et vous voulez que la propriété ne doit pas être associée avec d'autres objets.
5
répondu Rohit 2014-03-03 16:18:41
la source

cela a été longuement discuté, mais en Java tout est un pointeur. Il ne fait aucune distinction entre les attributions 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, en fonction de vos exigences de mémoire. Les performances et l'utilisation de la mémoire sont plus déterministes en C++ (duh).

3
répondu cmollis 2014-04-04 01:17:41
la source
Object *myObject = new Object;

cela créera une référence à un objet (sur le tas) qui doit être supprimé explicitement pour éviter fuite de mémoire .

Object myObject;

cela créera un objet(myObject) du type automatique (sur la pile) qui sera automatiquement supprimé lorsque l'objet(myObject) sort de la portée.

3
répondu Palak Jain 2017-04-15 20:07:16
la source

un pointeur renvoie directement à l'emplacement mémoire d'un objet. Java n'a rien de ce genre. Java a des références qui font référence à l'emplacement de l'objet à travers des tables de hachage. Vous ne pouvez pas faire quelque chose comme l'arithmétique de pointeur en Java avec ces références.

pour répondre À votre question, c'est juste votre préférence. Je préfère utiliser le Java syntaxe.

1
répondu RioRicoRick 2014-04-04 01:15:29
la source

vous ne devrait pas . Les gens (beaucoup de gens, malheureusement) l'écrivent par ignorance.

parfois l'allocation dynamique a sa place mais, dans les exemples que vous donnez, c'est faux .

Si vous voulez penser à l'efficacité, c'est pire , parce qu'elle introduit indirection pour aucune bonne raison. Ce type de programmation est plus lent et plus sujet à erreur .

1
répondu Lightness Races in Orbit 2018-04-26 15:27:58
la source

avec des pointes ,

  • peut parler directement à la mémoire.

  • peut prévenir beaucoup de fuites de mémoire d'un programme en manipulant des pointeurs.

0
répondu lasan 2016-12-30 15:59:40
la source

une raison d'utiliser des pointeurs est d'interfacer avec les fonctions C. Une autre raison est de sauvegarder de la mémoire; par exemple: au lieu de passer un objet qui contient beaucoup de données et a un processeur-intensif copy-constructor à une fonction, il suffit de passer un pointeur à l'objet, sauver la mémoire et la vitesse surtout si vous êtes dans une boucle, cependant une référence serait mieux dans ce cas, sauf si vous utilisez un tableau de C-style.

0
répondu theo2003 2017-01-11 23:03:39
la source

dans les zones où l'utilisation de la mémoire est à son meilleur , les pointeurs est pratique. Par exemple, considérons un algorithme minimax, où des milliers de noeuds seront générés en utilisant la routine récursive, et ensuite les utiliser pour évaluer le prochain meilleur coup dans le 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-pointer continue à occuper de l'espace jusqu'à ce que son appel récursif retourne une valeur.

0
répondu seccpur 2018-02-18 20:11:52
la source

je vais inclure un cas d'utilisation importante de pointeur. Lorsque vous stockez un objet de la classe de base, mais il pourrait être polymorphe.

Class Base1 {
};

Class Derived1 : public Base1 {
};


Class Base2 {
  Base *bObj;
  virtual void createMemerObects() = 0;
};

Class Derived2 {
  virtual void createMemerObects() {
    bObj = new Derived1();
  }
};

dans ce cas, vous ne pouvez pas déclarer bObj comme un objet direct, vous devez avoir pointeur.

0
répondu user18853 2018-03-15 13:25:35
la source

"la Nécessité est 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, alors le passer en tant qu'objet copiera son état (ce que vous pourriez ne pas vouloir ..Et peut être de grands frais généraux) résultant ainsi en un frais généraux d'objet de copie .alors que le pointeur est fixe de 4 octets (en supposant 32 bits). D'autres raisons sont déjà mentionnés ci-dessus...

0
répondu sandeep bisht 2018-06-26 19:29:23
la source

il y a déjà beaucoup d'excellentes réponses, mais laissez-moi vous donner un exemple:

j'ai une classe D'article simple:

 class Item
    {
    public: 
      std::string name;
      int weight;
      int price;
    };

je fais un vecteur pour en tenir plusieurs.

std::vector<Item> inventory;

je crée un million d'objets, et je les renvoie sur le vecteur. Je trie Le Vecteur par nom, puis je fais une recherche binaire itérative simple pour un nom d'article particulier. J'ai tester le programme, et il prend plus de 8 minutes pour terminer l'exécution. Puis je change mon vecteur d'inventaire comme ceci:

std::vector<Item *> inventory;

...et créer mes millions d'objets Item via new. Les seules modifications que je fais à mon code sont d'utiliser les pointeurs vers les articles, à l'exception d'une boucle que j'ajoute pour le nettoyage de la mémoire à la fin. Ce programme fonctionne en moins de 40 secondes, ou mieux qu'une augmentation de vitesse de 10x. EDIT: le code est à http://pastebin.com/DK24SPeW Avec le compilateur optimisations il ne montre qu'une augmentation de 3,4 x sur la machine que je viens de tester, ce qui est encore considérable.

-4
répondu Darren 2015-02-06 18:20:03
la source

Autres questions sur c++ pointers c++11