Dev - C++ - Comportamiento extraño del Destructor()

 
Vista:
sin imagen de perfil

Comportamiento extraño del Destructor()

Publicado por Chris (7 intervenciones) el 26/09/2023 18:22:52
Buenas a todos, estoy resolviendo problemas sobre Estructuras de Datos, tengo este archivo de encabezado de Lista Enlazada descargado de la web donde hago los ejercicios.

LinkedList.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
using namespace std;
 
class Node {
    public:
        int value;
        Node* next;
        Node(int value) {
            this->value = value;
            next = nullptr;
        }
};
 
class LinkedList {
    private:
        Node* head;
        Node* tail;
        int length;
 
    public:
        LinkedList(int value) {
            Node* newNode = new Node(value);
            head = newNode;
            tail = newNode;
            length = 1;
        }
 
        ~LinkedList() {
            Node* temp = head;
            while (head != nullptr) {
                head = head->next;
                delete temp;
                temp = head;
            }
        }
 
        void printList() {
            Node* temp = head;
            if (temp == nullptr) {
                cout << "empty";
            } else {
                while (temp != nullptr) {
                    cout << temp->value;
                    temp = temp->next;
                    if (temp != nullptr) {
                        cout << " -> ";
                    }
                }
            }
            cout << endl;
        }
 
        Node* getHead() {
            return head;
        }
 
        Node* getTail() {
            return tail;
        }
 
        int getLength() {
            return length;
        }
 
        void makeEmpty() {
            Node* temp = head;
            while (head != nullptr) {
                head = head->next;
                delete temp;
                temp = head;
            }
            tail = nullptr;
            length = 0;
        }
 
        void append(int value) {
            Node* newNode = new Node(value);
            if (head == nullptr) {
                head = newNode;
                tail = newNode;
            } else {
                tail->next = newNode;
                tail = newNode;
            }
            length++;
        }
};

Este es una solución para uno de los ejercicios, comprobada con los test online, estoy haciendo mis propios test para aprender también sobre esto:

LL_find_middle_node.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution{
    public:
        static Node* findMiddleNode(LinkedList l) {
            Node* slow = l.getHead();
            Node* fast = slow;
            while (fast != nullptr && fast->next != nullptr) {
                slow = slow->next;
                fast = fast->next->next;
            }
            return slow;
        }
};
 
#define CATCH_CONFIG_MAIN
#include "../../catch.hpp"
 
TEST_CASE("Even number of elements", "[even_test]"){
    LinkedList l(1);
    l.append(2);
    l.append(3);
    l.append(4);
    l.append(5);
    l.append(6);
    Node *middle = Solution::findMiddleNode(l);
    REQUIRE(middle->value == 4);
}

Este test funciona bien, lo pongo como ejemplo para comparar lo que ocurre con el siguiente ejercicio.

LL_find_k_node_from_end.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution{
    public:
        static Node* findKthFromEnd(int k, LinkedList l){
            Node *fast = l.getHead();
            Node *slow = fast;
            for(int i = 0; i < k; ++i){
                if(fast == nullptr){
                    return nullptr;
                }
                fast = fast->next;
            }
            while(fast != nullptr){
                slow = slow->next;
                fast = fast->next;
            }
            return slow;
        }
};
 
#define CATCH_CONFIG_MAIN
#include "../../catch.hpp"
 
TEST_CASE("K Second From End", "[KSecondFromEnd]"){
    LinkedList l(1);
    l.append(2);
    l.append(3);
    l.append(4);
    l.append(5);
 
    Node *result = Solution::findKthFromEnd(2, l);
 
    REQUIRE(result->value == 4);
}

En ambos casos cuando termina de ejecutarse el método Solution::method(), se llama al destructor de clase, entiendo que al pasar la lista por valor termina el ámbito de ejecución de la copia.

Puedo ver en el depurador como se destruye la lista original, ya que los punteros copia siguen apuntando a las mismas direcciones de memoria.

Lo curioso es que con el método Node* findMiddleNode(LinkedList l) al volver el flujo de ejecución al test_case, la lista original sigue ahí, aún habiendo visto como se destruía, y el test es superado.
Esto no pasa con el método Node* findKthFromEnd(int k, LinkedList l), la lista se destruye y el test no llega a terminar porque el puntero devuelto no apunta a una dirección válida (creo que esto es lo esperado en esta situación)

Puedo pasar la lista por referencia y funciona bien, pero me sorprende este comportamiento tan dispar. Más que una solución me gustaría entender que está pasando ya que ambos métodos son muy parecidos, ambos devuelven un puntero a un nodo, recorren la lista y no modifican su contenido.

¿Alguna idea?

Gracias
Valora esta pregunta
Me gusta: Está pregunta es útil y esta claraNo me gusta: Está pregunta no esta clara o no es útil
0
Responder

Comportamiento extraño del Destructor()

Publicado por Tom (65 intervenciones) el 28/11/2023 11:58:32
En principio, a tus métodos de Solution no estás pasando la lista por copia, sino por refeerencia (o sea un puntero).
Por otra parte cuando se hace delete() no se cambia el valor del puntero (esto provoca problemas de "use after free" por ejemplo).
En tu caso no sé exactamente qué está pasando (o sea no se cual es el problema) pero quizás tengas que fortificar un poco más tus métodos de Solution para comprobar más posibles casos de nullptr.

1
2
3
4
5
           // slow puede ser null ? slow->next puede ser null ?
            while (fast != nullptr && fast->next != nullptr) {
                slow = slow->next;
                fast = fast->next->next;
            }

En cualquier caso, cualquier cosa que hagas con la LinkedList fuera de la rutina en Solution es incorrecto.

También recomendaría que el dtor de LinkedList ponga a nullptr los nodos que libera con delete() ...
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar