Préface
Java n'a rien à voir avec C++, contrairement à ce qui est dit. La machine à fabriquer du Java voudrait vous faire croire que parce que Java a une syntaxe similaire à celle du C++, les deux langages sont similaires. Rien n'est plus éloigné de la vérité. Cette désinformation est en partie la raison pour laquelle les programmeurs Java se tournent vers le C++ et utilisent une syntaxe similaire à celle de Java sans comprendre les implications de leur code.
En avant, nous allons
Mais je n'arrive pas à comprendre pourquoi on devrait le faire de cette façon. Je suppose que cela que ça a à voir avec l'efficacité et la vitesse puisque nous avons un accès direct à l'adresse l'adresse mémoire. N'ai-je pas raison ?
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 (aka variables de pile) ont leurs destructeurs appelés une fois qu'elles sortent de la portée. Par exemple :
{
std::string s;
}
// s is destroyed here
En revanche, si vous utilisez un pointeur alloué 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 le new
syntaxe répandue en C# et Java. Elles sont utilisées à des fins complètement différentes.
Avantages de l'allocation dynamique
1. Il n'est pas nécessaire de connaître à l'avance la taille du tableau.
L'un des premiers problèmes rencontrés par de nombreux programmeurs C++ est que, lorsqu'ils acceptent des entrées arbitraires de la part des utilisateurs, vous ne pouvez allouer qu'une taille fixe à une variable de pile. Vous ne pouvez pas non plus 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 en interne, ce qui ne devrait pas poser de problème. Mais la solution à ce problème est essentiellement l'allocation dynamique. Vous pouvez allouer de la mémoire dynamique en fonction de 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 complémentaire : Une erreur que font beaucoup de débutants est l'utilisation de tableaux de longueur variable. Il s'agit d'une extension de GNU et également de Clang parce qu'ils reflètent de nombreuses extensions de GCC. Ainsi, le tableau suivant int arr[n]
ne doit pas être invoquée.
Comme le tas est beaucoup plus grand que la pile, on peut arbitrairement allouer/réallouer autant de mémoire que nécessaire, alors que la pile a une limite.
2. Les tableaux ne sont pas des pointeurs
En quoi est-ce un avantage, me demandez-vous ? La réponse deviendra claire une fois que vous aurez compris la confusion/le mythe derrière les tableaux et les pointeurs. On pense généralement qu'ils sont identiques, mais ce n'est pas le cas. Ce mythe vient du fait que les pointeurs peuvent être souscrits tout comme les tableaux et que les tableaux se transforment en pointeurs au niveau le plus élevé de la déclaration d'une fonction. Cependant, une fois qu'un tableau se transforme en pointeur, le pointeur perd son statut d'objet. sizeof
informations. Ainsi, sizeof(pointer)
donnera la taille du pointeur en octets, qui est généralement de 8 octets sur un système 64 bits.
Vous ne pouvez pas assigner 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 tout ce que vous voulez avec les pointeurs. Malheureusement, comme la distinction entre les pointeurs et les tableaux est présentée de façon très souple en Java et en C#, les débutants ne comprennent pas la différence.
3. Polymorphisme
Java et C# disposent de facilités qui vous permettent de traiter les objets comme un autre, par exemple en utilisant la fonction as
mot-clé. Donc si quelqu'un voulait traiter un Entity
en tant qu'objet Player
on pourrait faire Player player = Entity as Player;
Ceci est très utile si vous avez l'intention d'appeler des fonctions sur un conteneur homogène qui ne doivent s'appliquer qu'à un type spécifique. Cette fonctionnalité peut être réalisée de manière similaire 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();
}
Ainsi, si seuls les Triangles disposent d'une fonction Rotate, une erreur de compilation se produirait si vous essayiez de l'appeler sur tous les objets de la classe. Utilisation de dynamic_cast
vous pouvez simuler le as
mot-clé. Pour être clair, si un cast échoue, il renvoie un pointeur invalide. Donc !test
est essentiellement un raccourci pour vérifier si test
est NULL ou un pointeur invalide, ce qui signifie que le transfert a échoué.
Avantages des variables automatiques
Après avoir vu toutes les possibilités offertes par l'allocation dynamique, vous vous demandez probablement pourquoi personne n'utiliserait l'allocation dynamique tout le temps ? Je vous ai déjà donné 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 :
-
Elle est source d'erreurs. L'allocation manuelle de mémoire est dangereuse et vous êtes sujet à des fuites. Si vous ne maîtrisez pas l'utilisation du débogueur ou de la fonction valgrind
(un outil de fuite de mémoire), vous risquez de vous arracher les cheveux. Heureusement, les idiomes RAII et les pointeurs intelligents atténuent un peu ce problème, mais vous devez être familier avec des pratiques telles que la règle de trois et la règle de cinq. Cela fait beaucoup d'informations à assimiler, et les débutants qui ne savent pas ou qui ne s'en soucient pas tomberont dans ce piège.
-
Ce n'est pas nécessaire. Contrairement à Java et C# où il est idiomatique d'utiliser l'attribut new
partout, en C++, vous ne devez l'utiliser que si vous en avez besoin. L'expression courante dit que tout ressemble à un clou si vous avez un marteau. Alors que les débutants en C++ sont effrayés par les pointeurs et apprennent à utiliser les variables de pile par habitude, les programmeurs Java et C# commencer en utilisant des pointeurs sans les comprendre ! C'est littéralement partir du mauvais pied. Vous devez abandonner tout ce que vous savez, car la syntaxe est une chose, l'apprentissage du langage en est une autre.
1. (N)RVO - Alias, Optimisation de la valeur de retour (nommée)
Une optimisation que beaucoup de compilateurs font sont des choses appelées élision y optimisation de la valeur de retour . Ces éléments peuvent éviter des copies inutiles, ce qui est utile pour les objets de très grande taille, comme un vecteur contenant de nombreux éléments. Normalement, la pratique courante est d'utiliser des pointeurs sur transfert de propriété plutôt que de copier les gros objets dans déplacer les entourer. Cela a conduit à la création de sémantique du déplacement y pointeurs intelligents .
Si vous utilisez des pointeurs, (N)RVO fait NO se produire. Il est plus avantageux et moins sujet aux erreurs de tirer parti de la (N)RVO plutôt que de renvoyer ou de passer des pointeurs si l'optimisation vous préoccupe. Des fuites d'erreurs peuvent se produire si l'appelant d'une fonction est chargé de delete
d'un objet alloué dynamiquement, etc. Il peut être difficile de suivre la propriété d'un objet si les pointeurs sont transmis comme une patate chaude. Utilisez simplement les variables de pile car c'est plus simple et plus efficace.
141 votes
Si vous ne voyez pas de raison d'utiliser des pointeurs, ne le faites pas. Préférez les objets. Préférez les objets à l'unique_ptr, au shared_ptr et aux pointeurs bruts.
126 votes
Note : en java, tout (sauf les types de base) est un pointeur. vous devriez donc plutôt vous demander l'inverse : pourquoi ai-je besoin d'objets simples ?
129 votes
Notez que, en Java, les pointeurs sont cachés par la syntaxe. En C++, la différence entre un pointeur et un non-pointeur est explicitée dans le code. Java utilise les pointeurs partout.
5 votes
En C++11, vous avez beaucoup moins souvent besoin de pointeurs.
8 votes
@KarolyHorvath Ils sont proches, mais pas identiques
236 votes
Fermer comme trop vaste ? Sérieusement ? S'il vous plaît les gens, notez que cette façon de programmer en Java++ est très courante et l'un des problèmes les plus importants de la communauté C++. . Il faut le traiter avec sérieux.
4 votes
Relié (mais pas un doublon) : Objets C++ : Quand dois-je utiliser un pointeur ou une référence ?
0 votes
@PeterMortensen J'ai pensé la même chose l'autre jour mais hélas, je me suis trompé. Peut-être que vous aussi. =)
1 votes
@Manu343726 si vous pensez que c'est trop large... et peut-être un peu conceptuel peut-être que cela devrait être sur programmers.stackexchange.com ...fwiw
0 votes
Cette question couvre une grande partie du même territoire, mais en tenant compte de la gestion moderne des pointeurs : Quel est l'objectif du pointeur scopé ?
1 votes
Que pensez-vous faire en Java ?
1 votes
Un programmeur Java ne sait pas qu'en Java chaque chose typée objet est implicitement par référence ?
1 votes
Protip : Utilisez make_unique (qui n'est pas standard mais bien pratiqué) ou make_shared au lieu d'utiliser le mot-clé new. Les ptrs nus sont mauvais. herbsutter.com/gotw/_102
0 votes
@DanielMarin ou plutôt, les "références" Java sont des pointeurs avec des opérateurs arithmétiques supprimés.
29 votes
Pourquoi devrais-je utiliser l'adresse d'une maison alors que je pourrais simplement utiliser la maison elle-même ? Au lieu de demander à Amazon d'envoyer mon colis au 123 Nowhere St, j'apporte simplement ma maison à l'entrepôt, ils y placent mes achats et je la rapporte.
0 votes
J'allais dire la même chose que @immibis, car un pointeur est plus léger qu'un objet.
2 votes
@immibis apportez une copie de votre maison à l'entrepôt, retournez une autre copie et attribuez-la à votre maison et soutenez le secteur de la construction.
0 votes
Cette question combine l'interrogation sur l'allocation du tas et de la pile (la déclaration "pointeur" utilise new) et la question de savoir pourquoi utiliser des pointeurs plutôt que de se référer à des objets par leur nom. La question/réponse mentionne également très peu les références c++.
0 votes
La raison en est simple : vous n'avez accès à l'objet que dans la portée dans laquelle il a été déclaré, et tout programme non trivial comporte des milliers d'autres portées dans lesquelles vous avez besoin de l'objet.