Разбираю исходнички интересной программки и натолкнулся на код, назначение которого мне понятно, а причина использования его непонятна. Т.е что код делает — ясно, но почему код написан именно так?
Вот кусочек, все лишнее я повыкидывал.
class List;
class Node
{
public:
Node();
void InsertAfter(Node *pred);
private:
friend class List;
Node *succ;
Node *pred;
void *ptr;
};
Здравствуйте, jd-Kevin, Вы писали:
JK>Разбираю исходнички интересной программки и натолкнулся на код, назначение которого мне понятно, а причина использования его непонятна. Т.е что код делает — ясно, но почему код написан именно так? JK>Вот кусочек, все лишнее я повыкидывал.
JK>class List;
JK>class Node
JK>{
JK>public:
JK> Node();
JK> void InsertAfter(Node *pred);
JK>private:
JK> friend class List;
JK> Node *succ;
JK> Node *pred;
JK> void *ptr;
JK>};
JK>inline
JK>Node::Node(void)
JK>: succ(0),
JK> pred(0),
JK> ptr(0)
JK>{
JK> // empty
JK>}
JK>inline
JK>void
JK>Node::InsertAfter(Node *pred)
JK>{
JK> Node *succ = pred->succ;
this->>pred = pred; // это по идее прицепление текущего объекта this->>succ = succ; // в список после заданноно указателм pred
pred->>succ = this;
succ->>pred = this;
JK>}
JK>class List
JK>{
JK>public:
JK> List();
JK> void AddHead(Node* n);
JK>private:
JK> Node* head;
JK> Node* tail;
JK> Node* tailpred;
JK>};
JK>List::List()
JK>{
this->>head = (Node *) &(this->tail); // простое преобразование типовthis->>tail = 0;
this->>tailpred = (Node *) &(this->head); // а это кольцевой список
JK>}
JK>inline
JK>void
JK>List::AddHead(Node *n)
JK>{
n->>InsertAfter((Node *) &(this->head));
JK>}
JK>void main()
JK>{
JK> List *log = new List;
JK> Node *node = new Node;
log->>AddHead(node);
JK>}
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Я понимаю, что делает программа. Мне непонятно, зачем разработчик берет адрес указателя преобразует его в Node и от него ссылается на лругие объекты. Не наглядно. Во-вторых — где написано, что данные класса располагаются в памяти в той же последовательности? А если я переставлю местами, например, в классе List head и tail.
Здравствуйте, jd-Kevin, Вы писали:
JK>Я понимаю, что делает программа. Мне непонятно, зачем разработчик берет адрес указателя преобразует его в Node и от него ссылается на лругие объекты. Не наглядно. Во-вторых — где написано, что данные класса располагаются в памяти в той же последовательности? А если я переставлю местами, например, в классе List head и tail.
Все рухнет. Автор кода пытается использовать заголовок списка ('List') в качестве элемента списка ('Node'), надеясь на совпадение layout-ов этих классов в памяти. Очень грубое и, в общем случае, неработоспособное решение. Пример того, как делать ни в коем случае нельзя.
Если уж автору так хотелось включить в цикл и сам заголовок списка, то можно было бы сделать один из 'Node' членом 'List'.
Здравствуйте, jd-Kevin, Вы писали:
JK>Здравствуйте, LaptevVV, Вы писали:
JK>Я понимаю, что делает программа. Мне непонятно, зачем разработчик берет адрес указателя преобразует его в Node и от него ссылается на лругие объекты. Не наглядно. Во-вторых — где написано, что данные класса располагаются в памяти в той же последовательности? А если я переставлю местами, например, в классе List head и tail.
Это — плод кривого проектирования.
Должно делать так:
class Node
{
// связи
Node *prev_, *next_;
// управление связямиvoid insert_next(Node* node); // добавляет узел послеvoid insert_prev(Node* node); // добавляет узел передvoid remove_this(); // выцепляет этот узел из списка
// осмысленные данные
data_t data_;
public:
Node() {}
Node(const data_t& data) : data_(data) {}
Node* prev() const { return prev_; }
Node* next() const { return next_; }
void insert_after(const data_t& data) { insert_next(new Node(data)); }
void insert_before(const data_t& data) { insert_prev(new Node(data)); }
void destroy() { remove_this(); delete this; }
};
class List
{
Node root_; // хрен с ним, с фиктивным данным
// Считаем, что next_ указывает на голову,
// а prev_ - на хвост.
// Если они оба указывают на *this, то список пуст.
// Для пущей безопасности можно было бы ввестиstatic Node null_;
Node rend_, end_; // rend_.prev_ = end_.next_ = &null_
// это позволило бы избежать кольцевого путешествия по списку через фиктивный root_...public:
bool empty() const { return begin() == end(); }
Node* begin() const { return rend()->next(); }
Node* rbegin() const { return end()->prev(); }
Node* end() const { return &root_; }
Node* rend() const { return &root_; }
void push_front(const data_t& data) { rend()->insert_after(data); }
void push_back(const data_t& data) { end()->insert_before(data); }
void pop_front() { destroy(begin()); }
void pop_back() { destroy(rbegin()); }
};
И увидим, что не вводя явно фиктивного концевика, разработчик его все-таки получил
Но, так как нет никаких гарантий (во всяком случае, они в коде не прозвучали явно) относительно смещений членов в структурах List и Node, то можно считать, что мы наблюдали грязный хак Но, все равно, код заслуживает внимания
Здравствуйте, Анатолий Широков, Вы писали:
АШ>Но, так как нет никаких гарантий (во всяком случае, они в коде не прозвучали явно) относительно смещений членов в структурах List и Node, то можно считать, что мы наблюдали грязный хак Но, все равно, код заслуживает внимания
А чтобы хак был чище — нужно завести структуру из двух указателей, Node и List унаследовать от нее, и делать static_cast (не reinterpret_cast !!!) к нужному типу.
К>А чтобы хак был чище — нужно завести структуру из двух указателей, Node и List унаследовать от нее, и делать static_cast (не reinterpret_cast !!!) к нужному типу.
Дядя Кодт, а я что? Я ничего
Я бы вообще малой кровью обошелся:
class List
{
public:
List()
{
head = &fiction;
fiction.succ = 0;
fiction.pred = head;
}
...
pivate:
Node* head;
Node fiction;
};
fiction не может нести в себе данные (или мы будем иметь сэкс при удалении их).
Следовательно, его можно использовать только в справочных целях.
fiction.succ (какой идентификатор!) указывает на первый элемент списка, а fiction.pred — на последний.
Если они указывают на сам fiction — список пуст.
Тогда нам не нужно держать указатель head.
Вообще, двусвязный список можно организовать несколькими способами:
в качестве узлов-терминаторов (перед началом и за концом) использовать NULL.
+ быстрая и
— кровавая диагностика выхода за границу
использовать универсальный read-only узел-заглушку (который сам на себя указывает)
+ быстрая и бескровная диагностика
— единожды попав в этот узел, невозможно вернуться назад
левая и правая заглушки (для каждого списка — свои)
+ попав в заглушку, можно пойти назад; попытка идти дальше — оказываешься на этой же заглушке
— два лишних экземпляра данных
первый и последний элементы — являются заглушками, но несут данные (соответственно, они размещены на куче)
+ ничего лишнего
— усложняется управление списком
универсальная заглушка (список свернут в кольцо с одним обязательным фиктивным элементом)
+ компактность
— попав в заглушку, можно перескочить и оказаться в голове (хвосте) списка