web-dev-qa-db-fra.com

Quand un objet est-il "hors de portée"?

En C++, quand un objet est-il défini comme "hors de portée"?

Plus précisément, si j'avais une liste liée individuellement, qu'est-ce qui définirait un objet de nœud de liste unique comme "hors de portée"? Ou si un objet existe et est référencé par une variable ptr, est-il correct de dire que l'objet est défini comme "hors de portée" au moment où la référence est supprimée ou pointe vers un autre objet?

MISE À JOUR: En supposant qu'un objet est une classe qui a un destructeur implémenté. Le destructeur sera-t-il appelé au moment où l'objet quitte la lunette?

if (myCondition) {
    Node* list_1 = new Node (3);
    Node* list_2 = new Node (4);
    Node* list_3 = new Node (5);

    list_1->next = list_2;
    list_2->next = list_3;
    list_3->next = null;
}

En d'autres termes, le Node pointé par list_1 appelle son destructeur après cette ligne:

Node* list_1 = new Node (3);

?

25
Pat Murray

Tout d'abord, souvenez-vous que les objets en C++ peuvent être créés soit sur la pile ou sur sur le tas.

Un cadre de pile (ou étendue) est défini par une instruction. Cela peut être aussi grand qu'une fonction ou aussi petit qu'un bloc de contrôle de flux (while/if/for etc.). Un arbitraire {} une paire contenant un bloc de code arbitraire constitue également une trame de pile. Toute variable locale définie dans une trame sera hors de portée une fois que le programme aura quitté cette trame. Lorsqu'une variable de pile sort de la portée, son destructeur est appelé.

Voici donc un exemple classique d'un cadre de pile (une exécution d'une fonction) et d'une variable locale déclarée à l'intérieur, qui sortira du cadre une fois le cadre de pile terminé - une fois la fonction terminée:

void bigSideEffectGuy () {
    BigHeavyObject b (200);
    b.doSomeBigHeavyStuff();
}
bigSideEffectGuy();
// a BigHeavyObject called b was created during the call, 
// and it went out of scope after the call finished.
// The destructor ~BigHeavyObject() was called when that happened.

Voici un exemple où nous voyons un cadre de pile qui n'est que le corps d'une instruction if:

if (myCondition) {
    Circle c (20);
    c.draw();
}
// c is now out of scope
// The destructor ~Circle() has been called

Le seul moyen pour un objet créé par pile de "rester dans la portée" après la fermeture du cadre est s'il s'agit de la valeur de retour d'une fonction. Mais ce n'est pas vraiment "rester dans la portée" parce que l'objet est copié. L'original sort donc du cadre, mais une copie est faite. Exemple:

Circle myFunc () {
    Circle c (20);
    return c;
}
// The original c went out of scope. 
// But, the object was copied back to another 
// scope (the previous stack frame) as a return value.
// No destructor was called.

Maintenant, un objet peut également être déclaré sur le tas. Pour les besoins de cette discussion, considérez le tas comme une tache de mémoire amorphe. Contrairement à la pile, qui alloue et désalloue automatiquement la mémoire nécessaire lorsque vous entrez et quittez des trames de pile, vous devez réserver et libérer manuellement la mémoire de tas.

Un objet déclaré sur le tas "survit", d'une façon ou d'une autre, entre des cadres de pile. On pourrait dire qu'un objet déclaré sur le tas ne sort jamais de la portée, mais c'est vraiment parce que l'objet n'est jamais vraiment associé à aucune portée. Un tel objet doit être créé via le mot clé new et doit être référencé par un pointeur.

Il est de votre responsabilité de libérer l'objet tas une fois que vous en avez terminé. Vous libérez des objets de tas avec le mot clé delete. Le destructeur sur un objet tas n'est pas appelé tant que vous n'avez pas libéré l'objet.

Les pointeurs qui font référence aux objets de tas sont eux-mêmes généralement des variables locales associées aux étendues. Une fois que vous avez fini d'utiliser l'objet tas, vous autorisez le ou les pointeurs s'y référant à sortir de la portée. Si vous n'avez pas libéré explicitement l'objet vers lequel pointe le pointeur, le bloc de mémoire de tas ne sera jamais libéré jusqu'à la fin du processus (c'est ce qu'on appelle une fuite de mémoire).

Pensez-y de cette façon: un objet créé sur la pile est comme un ballon scotché sur une chaise dans une pièce. Lorsque vous quittez la pièce, le ballon éclate automatiquement. Un objet créé sur le tas est comme un ballon sur un ruban, attaché à une chaise dans une pièce. Le ruban est le pointeur. Lorsque vous quittez la pièce, le ruban disparaît automatiquement, mais le ballon flotte juste au plafond et prend de la place. La procédure appropriée consiste à faire éclater le ballon avec une épingle, puis à quitter la pièce, après quoi le ruban disparaîtra. Mais, la bonne chose à propos du ballon sur la chaîne est que vous pouvez également détacher le ruban, le tenir dans votre main et sortir de la pièce et prendre le ballon avec vous.

Donc, pour aller à votre exemple de liste liée: généralement, les nœuds d'une telle liste sont déclarés sur le tas, chaque nœud tenant un pointeur sur le nœud suivant. Tout cela est sur le tas et ne sort jamais du cadre. La seule chose qui pourrait sortir de la portée est le pointeur qui pointe vers la racine de la liste - le pointeur que vous utilisez pour faire référence à la liste en premier lieu. Cela peut sortir du cadre.

Voici un exemple de création de choses sur le tas et le pointeur racine hors de portée:

if (myCondition) {
    Node* list_1 = new Node (3);
    Node* list_2 = new Node (4);
    Node* list_3 = new Node (5);

    list_1->next = list_2;
    list_2->next = list_3;
    list_3->next = null;
}
// The list still exists
// However list_1 just went out of scope
// So the list is "marooned" as a memory leak
49
sparc_spread
{ //scope is defined by the curly braces
    std::vector<int> vec;
}
// vec is out of scope here!
vec.Push_back(15);
5
Tony The Lion

Quand il quitte la portée dans laquelle il a été déclaré :)

Votre question telle qu'elle se présente n'est pas répondable sans voir la mise en œuvre. Cela revient à l'endroit où vous déclarez ce nœud.

void Foo()
{
    int i = 10;

    {
        int j = 20;
    } // j is out of scope

} // i is out of scope
2
Ed S.

"Hors champ" est une métonymie: comme dans, utiliser le nom ou la terminologie d'un concept pour parler de quelque chose de proche mais différent.

En C++, une étendue est une région statique du texte du programme, et donc quelque chose "hors de portée", pris littéralement, signifie physiquement en dehors d'une région de texte. Par exemple, { int x; } int y;: la déclaration de y est hors de la portée dans laquelle x est visible.

La métonymie "sortir du domaine" est utilisée pour exprimer l'idée que l'activation/instanciation dynamique de l'environnement associé à un certain domaine se termine. Et donc les variables définies dans cette portée disparaissent (donc "hors de portée").

Ce qui est en fait devenu "hors de portée", c'est le pointeur d'instruction, pour ainsi dire; l'évaluation du programme se déroule désormais dans un cadre qui n'a pas de visibilité sur celui-ci. Mais tout dans une portée ne disparaît pas! Les variables statiques seront toujours là la prochaine fois que la portée sera entrée.

"Sortir du cadre" n'est pas très précis, mais court et tout le monde comprend ce que cela signifie.

2
Kaz

Un objet qui est déclaré à l'intérieur d'une fonction (ou à l'intérieur de certaines constructions entre accolades et accolades à l'intérieur de fonctions) tombe hors de portée lorsque l'exécution quitte cette partie du code.

void some_func() {
  std::string x("Hello!");
  // x is in scope here
}
// But as soon as some_func returns, x is out of scope

Cela ne s'applique qu'aux éléments déclarés sur la pile, donc cela n'a pas grand-chose à voir avec les listes à liaison unique, car les nœuds de liste seront généralement instanciés sur le tas avec new.

Dans cet exemple, le pointeur renvoyé par new sortira de la portée à la sortie de la fonction, mais rien n'arrivera au Node lui-même:

void make_a_node() {
  Node* p = new Node;
} // Oh noes a memory leak!
2
DSimon