2016-07-21 1 views
1

Je suis nouveau en C++ et j'ai essayé de me familiariser avec la langue en implémentant une LinkedList.Un membre de classe C++ accédé via getter génère des erreurs, un accès direct ok mais std :: cout interfère?

class ListElement { 
public: 
    int val; 
    ListElement *next; 

    ListElement(int v, ListElement *n) {val = v; next = n;}; 
}; 

ListElement contient une valeur d'int val et un pointeur vers l'élément suivant de la liste (nullptr s'il n'y a pas d'élément suivant) et un constructeur.

class MyLinkedList { 
public: 
    ListElement *head; 

    MyLinkedList() {head = nullptr;}; 

    ListElement* getHead(void){ 
     return head; 
    }; 

    void append(int i) { 
     head = &ListElement(i, head); 
    }; 
}; 

MyLinkedList contient un pointeur vers le premier élément de la liste nommée head ainsi que des méthodes de travail sur la liste. En rencontrant quelques bugs dans ces méthodes j'ai essayé de traquer leur cause. (Je suis conscient qu'un getter pour un membre de la classe publique n'a pas de sens du tout, à l'origine head était privé.) Ce faisant, j'ai observé le comportement suivant, je ne peux pas expliquer:

int main() { 
    MyLinkedList l; 
    l.append(1); 

    int x = l.head->val; 
    cout << "head: " << x << "\n"; 
    int y = l.getHead()->val; 
    cout << "getHead(): " << y << "\n"; 
    int z = l.head->val; 
    cout << "head: " << z << "\n"; 

    cin.get(); 
    return 0; 
} 

L'exécution de ce code (ajouter #include <iostream> et using namespace std; pour un exemple de travail) imprime

head: 1 
getHead(): 18085840 
head: -858993460 

si le premier accès direct head fonctionne exactement comme prévu, ce qui donne 1 comme valeur du premier élément de la liste, mais en utilisant le getter retourne ordures. Si head est alors accessible directement à nouveau, il donne aussi des déchets, ce qui me fait penser à « Hm, semble comme l'utilisation getHead() tronque en quelque sorte l'objet ListMember », juste pour découvrir que

int x = l.head->val; 
cout << "head: " << x << "\n"; 
int z = l.head->val; 
cout << "head: " << z << "\n"; 

impressions

head: 1 
head: -858993460 

sans même toucher le getter. Donc, est-ce simplement en accédant à l.head d'une manière suffisante pour le brouiller?

Non, comme

int x = l.head->val; 
int z = l.head->val; 
cout << "head: " << x << "\n"; 
cout << "head: " << z << "\n"; 

retours (comme prévu) head: 1 deux fois. Donc en utilisant cout entre les changements mes objets ou leurs pointeurs? Et quel est le problème avec getHead() comme tout ce qu'il fait est juste return head;?


Donc, je suis assez perdu ici et je n'ai pas trouvé de questions directement liées. (This Question a un titre prometteur mais n'utilise pas de pointeurs). Suis-je totalement incapable d'utiliser les pointeurs de manière correcte? Ou y a-t-il une suppression automatique des objets dans les coulisses? Ou est-ce juste comment magiC++ fonctionne?

+2

'head = & ListElement (i, head);' - ce n'est pas comme cela que vous allouer un 'ListElement'. Allez lire à propos de 'new' (et dans la plupart des cas, essayez d'éviter d'en avoir besoin). – user2357112

+1

pour faire bonne mesure, vous pouvez également vouloir savoir sur [pointeurs intelligents] (http://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one – jaggedSpire

+0

@jaggedSpire Droit, mon mauvais - trop tard pour penser directement je suppose: \ – jpw

Répondre

1

En C++, vous devez gérer votre propre mémoire.L'instruction

ListElement(i, head); 

Crée une instance de ListElement localement délimité par MyLinkedList :: append(). Ainsi, une fois cette fonction terminée, la variable n'existe plus et le pointeur pointe désormais sur une mémoire non valide.

La raison pour laquelle votre première déclaration d'impression vous donne une réponse apparemment correcte est un peu un faux-fuyant. Dans tous les cas, vous accédez à une mémoire libre dont le comportement n'est pas défini. Dans le premier cas, la mémoire a juste la valeur que vous avez précédemment définie.

Vous devez allouer votre propre mémoire dans append et la nettoyer lorsque vous avez terminé. Une fois que vous avez maîtrisé "nouveau" assurez-vous de chercher comment parcourir une structure de données et supprimer chaque élément. Avec votre implémentation répertoriée liée, cela devrait être assez trivial.

1

changement

void append(int i) { 
    head = &ListElement(i, head); 
}; 

à

void append(int i) { 
    head = new ListElement(i, head); 
}; 

La première, si elle compile, prend l'adresse d'une pile temporaire objet alloué. Par conséquent head va "pointer sur les ordures" après sa destruction.

2

Dans

void append(int i) { 
    head = &ListElement(i, head); 
}; 

ListElement(i, head) crée un ListElement temporaire, sans nom et attribue un pointeur à head. Ensuite, parce que le ListElement lui-même n'a été affecté à rien, le ListElementgoes out of scope et est détruit. Cela laisse la tête pointant vers une mémoire invalide.

Head pourrait être écrit à utiliser la mémoire dynamique étendue la vie du ListElement

void append(int i) { 
    head = new ListElement(i, head); 
}; 

mais maintenant quelqu'un doit prendre la responsabilité de veiller à ce que le ListElement est supprimé lorsqu'il ne sera plus nécessaire.

Par exemple:

void remove(int i) { 
    // find list element i and previous element. Special handling required for first element 
    prev.next = element.next; 
    delete element; 
}; 

utilisation prudente des std::unique_ptr et std::move peut obtenir automatiser la gestion de la mémoire et d'éliminer la nécessité d'delete.