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
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?
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:
{
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. Ils sont utilisés à des fins complètement différentes.
Avantages de l'allocation dynamique
1. Vous n'avez pas besoin 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 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:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
Bien sûr, si vous avez utilisé un std::string
place, il se std::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:
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
Note latérale : Une erreur que de nombreux débutants font est l'utilisation de tableaux de longueur variable. Il s'agit d'une extension GNU et également de Clang car elles reflètent la plupart des extensions de GCC. Il
int arr[n]
ne faut donc pas se fier aux éléments suivants.
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.
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 / 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 sonsizeof
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:
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'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.
3. Polymorphisme
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 un Entity
objet comme un Player
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:
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 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 le as
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 ouvalgrind
(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.
1. (N) RVO - Aka, (Nommé) Optimisation de la valeur de retour
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.