1

Тема: Питання щодо Linked List

Вирішую зараз завдання на створення Linked List на LeetCode.
Самотужки я створити його не зміг, тому знайшов рішення в мережі, але мені треба розібратися в деталях.
Ось наприклад допоміжний клас:

class Node {
public:
    int data;
    Node* next;

    Node()
    {
        data = 0;
        next = NULL;
    }

    Node(int data)
    {
        this->data = data;
        this->next = NULL;
    }
};

Щодо конструкторів, то тут мені все зрозуміло.
Але не розумію Node* next.
Чому всередині, по суті, шаблону типу використано цей же таки тип?
Чому використали звичайний вказівник, а не розумний?

Подякували: leofun011

2

Re: Питання щодо Linked List

Teg Miles написав:

Але не розумію Node* next.

Я б радив спершу почитати, що таке той Linked List, як Data Structure. А, вже потім би брався його імплементовувати в певній мові.

Teg Miles написав:

по суті, шаблону типу використано цей же таки тип

1. Це не шаблон типу.
2. А, який тип там тоді мав би бути на вашу думку?

Teg Miles написав:

Чому використали звичайний вказівник, а не розумний?

Хз, поставте це питання автору, код якого ви позичили.

Подякували: leofun01, koala, lucas-kane3

3

Re: Питання щодо Linked List

wander написав:
Teg Miles написав:

Але не розумію Node* next.

Я б радив спершу почитати, що таке той Linked List, як Data Structure. А, вже потім би брався його імплементовувати в певній мові.

Teg Miles написав:

по суті, шаблону типу використано цей же таки тип

1. Це не шаблон типу.
2. А, який тип там тоді мав би бути на вашу думку?

Teg Miles написав:

Чому використали звичайний вказівник, а не розумний?

Хз, поставте це питання автору, код якого ви позичили.

2. Просто здалося дивним, що там вказівник сам на себе.

Ось що вийшло:

#include <cassert>
#include <iostream>
#include <memory>

using namespace std;

class Node {
public:
    int data;
    unique_ptr<Node> next;

    Node()
    {
        data = 0;
        next = NULL;
    };

    Node(int data)
    {
        this->data = data;
        this->next = NULL;
    };
};

class MyLinkedList {
private:
    unique_ptr<Node> head;
    unique_ptr<Node> tail;

public:
    MyLinkedList()
    {
        head = NULL;
        // tail = NULL;
    }

    int get(int index)
    {
        auto current = head.get();

        int count { 0 };
        while (current != NULL) {
            if (count == index) {
                return current->data;
            }
            ++count;
            current = current->next.get();
        }

        assert(0);
    }

    void addAtHead(int val)
    {
        unique_ptr<Node> new_node = make_unique<Node>(val);
        new_node->next = move(head);
        head = move(new_node);
    }

    void addAtTail(int val)
    {
        unique_ptr<Node> new_node = make_unique<Node>(val);
        if (head == NULL) {
            head = move(new_node);
        } else {
            auto temp_head = head.get();
            while (temp_head->next != NULL) {
                temp_head = temp_head->next.get();
            }
            temp_head->next = move(new_node);
        }
    }

    void addAtIndex(int index, int val)
    {
        auto current = head.get();
        unique_ptr<Node> new_node = make_unique<Node>(val);

        int count { 0 };
        while (current != NULL) {
            if (count == index - 1) {
                new_node->next = move(current->next);
                current->next = move(new_node);
            }
            ++count;
            current = current->next.get();
        }
    }

    void deleteAtIndex(int index)
    {
        auto current = head.get();

        if (index == 0) {
            head = head->next;
        }

        int count { 0 };
        while (current != NULL) {

            if (count == index - 1) {
                current->next = move(current->next->next);
            }
            ++count;
            current = current->next.get();
        }
    }
};

Але ніяк не виходить видалити елемент з індексом нуль.
Не можу перенести head->next в head,
видає помилку:
помилка: використання видаленої функції «std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Node; _Dp = std::default_delete<Node>]»
   95 |             head = head->next;

Пробував move використати, усе одно помилка виходить.

4

Re: Питання щодо Linked List

Teg Miles написав:
unique_ptr<Node> ...;
next = NULL; // не `nullptr`? ну ок..

Це, якось дивно бачити таке поєднання..

Teg Miles написав:

Пробував move використати, усе одно помилка виходить.

Певно компілятор бракбагований.

Подякували: leofun011

5

Re: Питання щодо Linked List

wander написав:
Teg Miles написав:
unique_ptr<Node> ...;
next = NULL; // не `nullptr`? ну ок..

Це, якось дивно бачити таке поєднання..

Teg Miles написав:

Пробував move використати, усе одно помилка виходить.

Певно компілятор бракбагований.

Ні, то програміст багований, бо вирішив використати move і .get() одночасно:)
Ось робоча версія, яку прийняв LeetCode:

#include <iostream>
#include <memory>

using namespace std;

class Node {
public:
    int data;
    unique_ptr<Node> next;

    Node()
    {
        data = 0;
        next = nullptr;
    };

    Node(int data)
    {
        this->data = data;
        this->next = nullptr;
    };
};

class MyLinkedList {
private:
    unique_ptr<Node> head;

public:
    MyLinkedList()
    {
        head = nullptr;
    }

    int get(int index)
    {
        auto current = head.get();

        int count { 0 };
        while (current != nullptr) {
            if (count == index) {
                return current->data;
            }
            ++count;
            current = current->next.get();
        }

        return -1;
    }

    void addAtHead(int val)
    {
        unique_ptr<Node> new_node = make_unique<Node>(val);
        new_node->next = move(head);
        head = move(new_node);
    }

    void addAtTail(int val)
    {
        unique_ptr<Node> new_node = make_unique<Node>(val);
        if (head == nullptr) {
            head = move(new_node);
        } else {
            auto temp_head = head.get();
            while (temp_head->next != nullptr) {
                temp_head = temp_head->next.get();
            }
            temp_head->next = move(new_node);
        }
    }

    void addAtIndex(int index, int val)
    {
        auto current = head.get();
        unique_ptr<Node> new_node = make_unique<Node>(val);

        if (index == 0) {
            new_node->next = move(head);
            head = move(new_node);
        } else {
            int count { 0 };
            while (current != nullptr) {
                if (count == index - 1) {
                    new_node->next = move(current->next);
                    current->next = move(new_node);
                }
                ++count;
                current = current->next.get();
            }
        }
    }

    void deleteAtIndex(int index)
    {
        auto current = head.get();

        if (index == 0) {
            head = move(head->next);
        } else {
            int count { 0 };
            while (current != nullptr) {

                if (count == index - 1 && current->next != nullptr) {
                    current->next = move(current->next->next);
                }
                if (count == index - 1 && current->next == nullptr) {
                    break;
                }
                ++count;
                current = current->next.get();
            }
        }
    }
};

6 Востаннє редагувалося steamwater (21.07.2024 20:36:53)

Re: Питання щодо Linked List

Teg Miles, std::unique_ptr не те що треба для linked_list, imho. Це тому що навiть якщо його застосувати корректно то всi видаленi ноди будуть жити допоки весь список не вийде з областi. Iнакше буде потрiбно кожного разу юзати методи release або reset. Це доволi недоречно. До того ж, коли ви на ступенi вивчення списку iнтiв, багато краще попрактикувати саме ручну роботу з памяттю. Семантіка володiння в списку i в смарт пойнтерi така, що потрiбно ресетити попередній на слiдуючий до того вузла, що має бути видалено. При додаваннi з голови, теж має сенс ресетити смарт на стару голову, на нову голову що примувала стару. Тут легше заплутатися нiж з натiвними вказiвниками. Тому раджу спочатку зробити на сирих вказiвниках.
Iндекси теж недоречнi. На попередньо сказаному рiвнi недоречно писати свiй iтератор. Тому методи-члени find(int), та вставки/видалення раджу робити по значенню. На додавання в вас так i є, а видалення по iндексу не має сенсу. Могутньою стороною спискiв є валiднiсть адресiв (iтераторiв) пiсля будь-якого змiнення контенту, а от iндекси стають невалiднi. До того ж вони не ефективнi у списку.

Подякували: leofun01, Teg Miles2

7

Re: Питання щодо Linked List

steamwater, все так, але другий параґраф це до творців задачі 707. Design Linked List (en, js).

Подякували: steamwater1

8 Востаннє редагувалося steamwater (22.07.2024 12:31:38)

Re: Питання щодо Linked List

leofun01, задачу можна створити будь-яку. А от питання, чи воно потрiбне, вимага значно бiльше iнтелекту. Едина доцiльна координата елементу списку це вказiвник на нього. Звiсно, ви можете дати завдання для студентiв, реалiзувати реверс iтератор для одно-скерованного списку. Але в чому мета? Показати, що життя це бiль? Чи дати глибоке розумiння випадку коли те що задано нiколи не треба робити у реальному життi? Я вважаю це безглуздим марнуванням часу i навчального потенцiалу студента у бiльшостi випадкiв. Бо коли дивишся на те що навiть не тiльки у студента часто й густо бракує глибини у базових питаннях, то завдання накшалт показаного вами, це відповiдь, чому так. Щоб результат був для голови, треба через неї i вчити. Доречи, в мене була спроба влаштуватися преподом у один з найрозкрученнiших приватних закладlв. Спiвбесiда була 3-рiвневою (чи 4-х? не пам'ятаю) ще й кожен рiвень складався з ряду завданнь. Пройшов. А потiм, подивившись навчальний план я був у шоцi i вiдразу запропонував написати новий. На тому ми й розпрощалися. Тут жертви не тiлькi батькiвськi грошi. Студенти та їх мотiвацiя, ось що страждає.

Подякували: leofun011

9

Re: Питання щодо Linked List

Не вдивлявся сильно в імплементацію ТС, але std::move викликатиме reset та release за вас, проте це все ще не означає, що не потрібно бути уважним та дивитись що куди мувається. Але, проблема коли список вийде за скоуп та почнеться рекурсивний виклик деструкторів до глибини n (де n довжина списку) -- залишається. Не те щоб цю проблему не можна було б вирішити, але тоді якось весь сенс у використані std::unique_ptr втрачається, на мою думку. Загалом розумні вказівники не зовсім були створені саме для таких випадків, їхній скоуп використання дещо інший. З іншої сторони, це теж можливість трохи набити синців та самостійно розібратися в проблемі.

Щодо індексів повністю згоден, але це ж задачка з LeetCode. Там швидше тебе намагаються "навчити" вирішувати конкретні завдання, а не дати глибоке розуміння предметної області.

Подякували: leofun01, steamwater2

10 Востаннє редагувалося steamwater (23.07.2024 14:02:33)

Re: Питання щодо Linked List

wander написав:

Не вдивлявся сильно в імплементацію ТС, але std::move викликатиме reset та release за вас, проте це все ще не означає, що не потрібно бути уважним та дивитись що куди мувається.

Згоден. Але move скрива логiку, поперше. Це не дозволя тренувати саме те що потрiбно у контекстi завдання. До того ж це  така концепцiя, що лежить значно далi вiд соморобного списку, як i unique_ptr. До того ж воно важче сприймаєтья. Не дарма ви кажетете: "не вдивлявся", бо воно вимагає вдивлятися. Доречи, воно наче працює, дiйсно.

wander написав:

Але, проблема коли список вийде за скоуп та почнеться рекурсивний виклик деструкторів до глибини n (де n довжина списку) -- залишається.

Там нема такої проблеми. Деструктора у Node нема, тому delete на next викликатися не буде. Усi деструктори смартiв будуть запущенi у порядку зворотньому до порядку їх створення. Проблема у тому, що все це вiдбуватиметься у момент, коли увесь список виходитиме з областi. Тобто час життя unique_ptr не буде пов'язаний з реальою потребою у його цiлi. Результатом буде ненажерливiсть такого списку. Чим блище його скоуп на стецi до точки входу, та чим бiльше транзакцiй на додавання/видалення, тим бiлбше буде роздмухуватися цей пузир у пам'ятi, та тим довше вiн буде жити.
Що до некогерентних до здорового глузду i логіки задач (iндекси у даному разi), у контекстi теми самого завдання, то я свою точку зору висловив. Час i енергїя учня, це ресурс який треба використати якомога краще. Тому в мене нема чого сказати гарного про LeetCode i багато iншiх ресурсiв та закладiв, незалежно вiд рiвня розкурученностi.

11

Re: Питання щодо Linked List

steamwater написав:

Там нема такої проблеми. Деструктора у Node нема, тому delete на next викликатися не буде. Усi деструктори смартiв будуть запущенi ву порядку зворотньому до порядку їх створення. Проблема у тому, що все це вiдбуватимется у момент, коли увесь список виходитиме з областi.

Я й говорив за деструктори розумних вказівників. Явного деструктора немає і в самому списку, там всюди дефолтний, який буде викликати деструктор розумного вказівника. Починаючи з unique_ptr на перший вузол, його деструктор викличе видалення на цьому вузлі. Перш ніж цей виклик повернеться, він повинен знищити наступний вказівник цього вузла, чий деструктор розумного наступного (next) вказівника викличе delete на другому вузлі. І так далі, ланцюжок із n рекурсивних викликів. Що потенційно може призвести до stack overflow та UB.

cppreference самі про це говорять :)

https://en.cppreference.com/w/cpp/memory/unique_ptr#Example написав:
// unique_ptr-based linked list demo
struct List
{
    struct Node
    {
        int data;
        std::unique_ptr<Node> next;
    };
 
    std::unique_ptr<Node> head;
 
    ~List()
    {
        // destroy list nodes sequentially in a loop, the default destructor
        // would have invoked its `next`'s destructor recursively, which would
        // cause stack overflow for sufficiently large lists.
        while (head)
        {
            auto next = std::move(head->next);
            head = std::move(next);
        }
    }
};
steamwater написав:

Що до некогерентних до здорового глузду i логіки задач (iндекси у даному разi), у контекстi теми самого завдання, то я свою точку зору висловив. Час i енергїя учня, це ресурс який треба використати якомога краще. Тому в мене нема чого сказати гарного про LeetCode i багато iншiх ресурсiв та закладiв, незалежно вiд рiвня розкурученностi.

Як я знаю (можу помилятись) попередньо LeetCode є збірником різних завдань, які можуть вас попросити розв'язати на технічній співбесіді при прийнятті на роботу (деякі завдання можуть доходити до абсурду), саме тому деякі з них містять вже готові заготовки коду, які просять вас імплементувати, як це часто роблять на співбесідах. Я особисто знаю людину, яка вирішила там кілька сотень завдань саме для проходження співбесід, можливо автор цієї тем теж хоче знайти роботу. Що ж, який попит..

---------------------------

steamwater написав:

del ненавмисної копiї. Питання до модераторiв: як редагувати я бачу, а от як видалити нi. Якщо це можливо, то як?

Видаляти можуть лише модератори, якщо хочете можу видалити за вас.

Подякували: steamwater, leofun012

12 Востаннє редагувалося steamwater (23.07.2024 21:27:09)

Re: Питання щодо Linked List

wander написав:

Починаючи з unique_ptr на перший вузол, його деструктор викличе видалення на цьому вузлі. Перш ніж цей виклик повернеться, він повинен знищити наступний вказівник цього вузла, чий деструктор розумного наступного (next) вказівника викличе delete на другому вузлі

Так, ви правi. Вибачте, не подумав. Next є членом i тому ланцюгова рекцiя може бути подолана тiльки якось так:

~Node()
{
     next.release();
}

Дiйсно. Цікаво, чи не трапиться багаторазова спроба подвiйного видалення? Уявiмо, що усi ноди добавлялися з голови, окрiм останнього, якiй було вставлено десь у середину (по iндексу). Тож рекурсiя почнеться з нього. Потiм почнеться видалення з голови. Коли черга дiйде до вказаного нода, з якого почався перший ланцюг, схоже буде спроба повторного запуску його деструктора, та замаху на повторне звiльнення пам'ятi. Спека i бракуе часу самому перевiрити. Але, повторюю, unique_ptr загостренний на штучне використання на об'єтах не пов'язаних у структури. Тому час житття має бути незалежним.

wander написав:

якщо хочете можу видалити за вас

Так. Будь ласка, - той пост де Del. :)

13

Re: Питання щодо Linked List

steamwater написав:

Цікаво, чи не трапиться багаторазова спроба подвiйного видалення? Уявiмо, що усi ноди добавлялися з голови, окрiм останнього, якiй було вставлено десь у середину (по iндексу). Тож рекурсiя почнеться з нього. Потiм почнеться видалення з голови. Коли черга дiйде до вказаного нода, з якого почався перший ланцюг, схоже буде спроба повторного запуску його деструктора, та замаху на повторне звiльнення пам'ятi.

Ні, подвійного видалення не буде

{
    MyLinkedList myList;
    myList.addAtHead(10);
    myList.addAtHead(20);
    myList.addAtIndex(1, 15);
} // <-- call `myList` destructor `MyLinkedList::~MyLinkedList()`
  //    +--> call `head` destructor `unique_ptr<Node>::~unique_ptr<Node>()` [початок рекурсивного ланцюжка]
  //       +--> call `ptr` destructor `Node::~Node()`
  //          +--> call `next` destructor `unique_ptr<Node>::~unique_ptr<Node>()`
  //             +--> call `ptr` destructor `Node::~Node()`
  //                +--> call `next` destructor `unique_ptr<Node>::~unique_ptr<Node>()`
  //                 ...

При виході зі скоупу почнеться виклик дефолтного деструктора MyLinkedList::~MyLinkedList(), оскільки він дефолтний він просто тригерне деструктори мемберів, у нашому випадку маємо лише одного мембера -- unique_ptr<Node> head. Після чого ми потрапимо в деструктор Node::~Node(), який теж дефолтний та просто почне виклик деструктор свого мембера, тобто std::unique_ptr<Node> next. Ну і так далі. Видалення в цьому випадку можливе лише з першого вузла (голови), адже це єдиний член списку. Рекурсія не може початись з останнього вузла. Тому не важливо, куди ми впихнемо останнє значення в список (на початок, в середину чи кінець).

steamwater написав:

Так. Будь ласка, - той пост де De.

Видалив.

Подякували: leofun01, steamwater2

14 Востаннє редагувалося steamwater (23.07.2024 21:26:43)

Re: Питання щодо Linked List

Знов поквапився я з реплiлiкою. Мало часу коли є свiтло. I спека) Дiйсно, першим створюєтьcя сам список, тож його деструктор розпочне розматувати видалення починаючи з поля head. Але якщо десь зостанеться хоча б один видаленний з середини нод i його некст не буде зарелiзений, тодi й виникне проблема. Деструктор з релiзом теж погана iдея) Саме у методi видалення треба про це подбати.
Дякую, що видалили.

15

Re: Питання щодо Linked List

Дякую Teg Miles за цей топік. Якщо іґнорити абсурд самої ідеї діставати елементи за індексом з такої структури даних, то задача навіть цікава.

Найшов час глянути пропоноване рішеня і .. це викликало фантомні болі, довелось писати власне. Ніщо так не мотивує, як дивитись коли хтось робить не правильно.
Код даю

#define v val
#define p prev
#define n next
#define S(s, t, o, o_init) \
    struct s {             \
        t v; o p; o n;     \
        s(t v) : v(v), p(o_init), n(o_init) { } \
        s(t v, o p, o n) : v(v), p(p), n(n) { } \
    }
#define N node_t
#define C node_const_t
S(N, int, N *, this);
S(C, int const, C const *const, this);

#define i index
#define o origin
#define L MyLinkedList
union L {
    public:
        struct {
            int const length;
            C const *const tail;
            C const *const head;
        };
        // C const c;
    protected:
        N o;
        int l;
    public:
        L() : o(0) { }
        ~L() { deleteNodes(); }
    protected:
        N *getNode(int i) {
            int c = l + 1, h = c >> 1;
            i = (i + 1 + h) % c - h;
            N *x = &o;
            while(i < 0) { x = x->p; ++i; }
            while(0 < i) { x = x->n; --i; }
            return x;
        }
        void addAfterNode(N *p, int v) {
            p->n = p->n->p = new N(v, p, p->n);
            ++l;
        }
        void deleteNode(N *x) {
            --l;
            x->p->n = x->n;
            x->n->p = x->p;
            delete x;
        }
    private:
        // This method deletes all nodes except the origin.
        // All additional memory allocated by the instance will be free.
        // The state of the instance will be corrupted after deleteNodes() call.
        // Please make sure you validated the state before use of the instance.
        void deleteNodes() {
            N *x = o.n, *n = o.n->n;
            while(x != &o) {
                delete x;
                x = n;
                n = n->n;
            }
        }
    public:
        int get(int i) {
            if(i < 0 || l <= i) return -1;
            return getNode(i)->v;
        }
        void addAtHead(int v) {
            addAfterNode(&o, v);
        }
        void addAtTail(int v) {
            addAfterNode(o.p, v);
        }
        void addAtIndex(int i, int v) {
            if(i < 0 || l < i) return;
            addAfterNode(getNode(--i), v);
            // addAfterNode(getNode(i)->p, v);
        }
        void deleteAtIndex(int i) {
            if(i < 0 || l <= i) return;
            deleteNode(getNode(i));
        }
        void clear() {
            l = 0;
            deleteNodes();
            o.p = o.n = &o;
        }
};

#undef v
#undef p
#undef n
#undef S
#undef N
#undef C

#undef i
#undef o
#undef L
  • #define'и додав, бо мені код з короткими ідентифікаторами гарніший [і я скупий на символи];

  • Структура вузла винесена, бо я даю користувачу (собі) можливість читати вміст списків;

  • class замінив на union, бо я не хочу пхати reinterpret_cast;

  • Кільце дозволяє уникнути [всіх] перевірок nullptr;

  • В getNode(int) фокуси з індексом дають вибрати короткий шлях;

16

Re: Питання щодо Linked List

leofun01 написав:

Ніщо так не мотивує, як дивитись коли хтось робить не правильно.

А, хто казав, що його код не правильний? Код пана Teg Miles принаймні читабельний.

leofun01 написав:

#define'и додав, бо мені код з короткими ідентифікаторами гарніший [і я скупий на символи]

То візьміть пайтона та пишіть на ньому. Певен там можна все в 1 рядок написати :D

Подякували: leofun01, steamwater2

17

Re: Питання щодо Linked List

wander написав:

Код пана Teg Miles принаймні читабельний.

Допустимо, але ж алґоритми .. Добре, код перепишу, сподіваюсь що не читабельними видались саме макроси.

wander написав:
leofun01 написав:

#define'и додав, бо мені код з короткими ідентифікаторами гарніший [і я скупий на символи]

То візьміть пайтона та пишіть на ньому. Певен там можна все в 1 рядок написати :D

Може й так. Але запхати все в 1 рядок жертвуючи структурою і читабельністю це для мене не прийнятно.
Крім того, Python не дає такого контролю над памятю який є в C++.

Код (з явними структурами)
#define v val
#define p prev
#define n next
// #define i index
// #define o origin
// #define N node_t
// #define C node_const_t

union MyLinkedList {
    public:
        static struct C {
            int const v;
            C const *const p, *const n;
        };
        struct {
            int const length;
            C const *const tail, *const head;
        };
    protected:
        struct N {
            int v;
            N *p, *n;
        } o;
        int l;
    public:
        MyLinkedList() : o({ 0, &o, &o }) { }
        ~MyLinkedList() { deleteNodes(); }
    protected:
        N *getNode(int i) {
            int c = l + 1, h = c >> 1;
            i = (i + 1 + h) % c - h;
            N *x = &o;
            while(i < 0) { x = x->p; ++i; }
            while(0 < i) { x = x->n; --i; }
            return x;
        }
        void addAfterNode(N *p, int v) {
            p->n = p->n->p = new N({ v, p, p->n });
            ++l;
        }
        void deleteNode(N *x) {
            --l;
            x->p->n = x->n;
            x->n->p = x->p;
            delete x;
        }
    private:
        void deleteNodes() {
            N *x = o.n, *n = o.n->n;
            while(x != &o) {
                delete x;
                x = n;
                n = n->n;
            }
        }
    public:
        int get(int i) {
            if(i < 0 || l <= i) return -1;
            return getNode(i)->v;
        }
        void addAtHead(int v) {
            addAfterNode(&o, v);
        }
        void addAtTail(int v) {
            addAfterNode(o.p, v);
        }
        void addAtIndex(int i, int v) {
            if(i < 0 || l < i) return;
            addAfterNode(getNode(--i), v);
        }
        void deleteAtIndex(int i) {
            if(i < 0 || l <= i) return;
            deleteNode(getNode(i));
        }
        void clear() {
            l = 0;
            deleteNodes();
            o.p = o.n = &o;
        }
};

#undef v
#undef p
#undef n
// #undef i
// #undef o
// #undef N
// #undef C

Якщо замість 1-символьних поставити повні ідентифікатори і видалити #define'и, то код більш читабельним не стане. Закоментовані #def'и можна видалити, якщо заважають.

18

Re: Питання щодо Linked List

Справа не лише в #define'ах, ви ходите по дуже тонкому льоду.

leofun01 написав:
union MyLinkedList { // Чому `union`???

    /*
    Яка роль цих структур? Для чого вони? Це якийсь особливий прийом обфускації?)
    static struct C { // І чому тут `static`?
        int const v;
        C const *const p, *const n;
    };
    struct {
        int const length;
        C const *const tail, *const head;
    };
    */

    ...

    // Об'єкт `o` (він же член класу), ще не розпочав час свого життя.
    // Коли ініціалізація `o({ 0, &o, &o })` виконується, об'єкт `o` ще не ініціалізовано,
    // і взяття його адреси або використання для самоініціалізації є невизначеною поведінкою.
    // Це тому, що `o` використовується до завершення його ініціалізації.
    MyLinkedList() : o({ 0, &o, &o }) { }

    N *getNode(int i) {
        int c = l + 1 // << UB, спроба прочитати неактивний мембер `l` юніона.
        ...
    }
    void addAfterNode(N *p, int v) {
        ...
        ++l; // << UB, спроба прочитати неактивний мембер `l` юніона.
    }
    void deleteNode(N *x) {
        --l; // Теж саме, в інших функціях ті ж проблеми з активними/неактивними мемберами.
        ...;
    }

    ...
};

MyLinkedList myList;
myList.addAtHead(5);
auto i = myList.get(0);
leofun01 написав:

Якщо замість 1-символьних поставити повні ідентифікатори і видалити #define'и, то код більш читабельним не стане.

Ну, кому як.

Подякували: leofun011

19

Re: Питання щодо Linked List

Teg Miles, даруйте але помiркувавши, я зрозумiв що видаленi ноди таки будуть знищуватися вiдразу по виходi з методу видалення. Стало цiкаво i довелося перевiрити. Я нiколи не використовував unique_ptr там де потрiбна семантiка значення, тому мiй протест є цiлком природнiм. I думка не змiнилася. Я б радше shared_ptr використав, якщо закортiло би) Якщо цiкаво, от що вийшло:

#include <iostream>
#include <memory>

class Node {
public:
    int data;
    std::unique_ptr<Node> next;

    Node(int data_ = 0)
        : data(data_)
        , next(nullptr)
    { };
    ~Node() {
        std::cout << "\ndesructor Node\n";
    };
};
class MyLinkedList {
public:
    std::unique_ptr<Node> head;

    MyLinkedList(int first_elem)
        : head(new Node(first_elem))
    { }
    ~MyLinkedList() {
        if(head.get() != nullptr)
            delete_all();
    }
    void addAtHead(int val) {
        auto old_head = std::move(head);
        auto tobe_new_head = std::unique_ptr<Node>(new Node(val));
        head = std::move(tobe_new_head);
        head->next = std::move(old_head);
    }
    Node *delete_head() {
        head = std::move(head->next);
        return head.get();
    }
    void delete_all() {
        while(delete_head() != nullptr);
    }
    void print() {
        std::cout << "\nlist content:\n";
        auto current = head.get();
        for(; current != nullptr; current = current->next.get()) {
            std::cout << current->data << ' ';
        }
        cout << "\n";
    }
};

int main() {
    MyLinkedList lst(1);
    lst.addAtHead(2);
    lst.addAtHead(3);
    lst.print();
    {
        lst.delete_head();
        // there we have message from the deleted head destructor\
        before the next one: "\nhead deleted\n"
    }
    std::cout << "\nhead deleted\n";
    std::cout << "\nnew 321\n";
    lst.addAtHead(1);
    lst.addAtHead(2);
    lst.addAtHead(3);
    lst.print();
    return 0;
}

20

Re: Питання щодо Linked List

steamwater написав:

Teg Miles, даруйте але помiркувавши, я зрозумiв що видаленi ноди таки будуть знищуватися вiдразу по виходi з методу видалення. Стало цiкаво i довелося перевiрити. Я нiколи не використовував unique_ptr там де потрiбна семантiка значення, тому мiй протест є цiлком природнiм. I думка не змiнилася. Я б радше shared_ptr використав, якщо закортiло би) Якщо цiкаво, от що вийшло:

#include <iostream>
#include <memory>

class Node {
public:
    int data;
    std::unique_ptr<Node> next;

    Node(int data_ = 0)
        : data(data_)
        , next(nullptr)
    { };
    ~Node() {
        std::cout << "\ndesructor Node\n";
    };
};
class MyLinkedList {
public:
    std::unique_ptr<Node> head;

    MyLinkedList(int first_elem)
        : head(new Node(first_elem))
    { }
    ~MyLinkedList() {
        if(head.get() != nullptr)
            delete_all();
    }
    void addAtHead(int val) {
        auto old_head = std::move(head);
        auto tobe_new_head = std::unique_ptr<Node>(new Node(val));
        head = std::move(tobe_new_head);
        head->next = std::move(old_head);
    }
    Node *delete_head() {
        head = std::move(head->next);
        return head.get();
    }
    void delete_all() {
        while(delete_head() != nullptr);
    }
    void print() {
        std::cout << "\nlist content:\n";
        auto current = head.get();
        for(; current != nullptr; current = current->next.get()) {
            std::cout << current->data << ' ';
        }
        cout << "\n";
    }
};

int main() {
    MyLinkedList lst(1);
    lst.addAtHead(2);
    lst.addAtHead(3);
    lst.print();
    {
        lst.delete_head();
        // there we have message from the deleted head destructor\
        before the next one: "\nhead deleted\n"
    }
    std::cout << "\nhead deleted\n";
    std::cout << "\nnew 321\n";
    lst.addAtHead(1);
    lst.addAtHead(2);
    lst.addAtHead(3);
    lst.print();
    return 0;
}

Щиро кажучи, я не замислювався про семантику значення, просто використав розумний вказівник, бо в усіх підручниках кажуть, що сирі не варто використовувати взагалі.
Використав саме unique_ptr, щоб отримати більше досвіду, бо мало з ним працював.