Поясните кусочек кода
От: jd-Kevin Россия  
Дата: 01.09.03 13:43
Оценка:
Разбираю исходнички интересной программки и натолкнулся на код, назначение которого мне понятно, а причина использования его непонятна. Т.е что код делает — ясно, но почему код написан именно так?
Вот кусочек, все лишнее я повыкидывал.

class List;

class Node
{
public:
Node();
void InsertAfter(Node *pred);
private:
friend class List;
Node *succ;
Node *pred;
void *ptr;
};

inline
Node::Node(void)
: succ(0),
pred(0),
ptr(0)
{
// empty
}

inline
void
Node::InsertAfter(Node *pred)
{
Node *succ = pred->succ;
this->pred = pred;
this->succ = succ;
pred->succ = this;
succ->pred = this;
}

class List
{
public:
List();
void AddHead(Node* n);

private:
Node* head;
Node* tail;
Node* tailpred;
};

List::List()
{
this->head = (Node *) &(this->tail);
this->tail = 0;
this->tailpred = (Node *) &(this->head);
}

inline
void
List::AddHead(Node *n)
{
n->InsertAfter((Node *) &(this->head));
}

void main()
{
List *log = new List;
Node *node = new Node;
log->AddHead(node);
}
Re: Поясните кусочек кода
От: LaptevVV Россия  
Дата: 01.09.03 13:49
Оценка:
Здравствуйте, 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>}
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Поясните кусочек кода
От: jd-Kevin Россия  
Дата: 01.09.03 13:59
Оценка:
Здравствуйте, LaptevVV, Вы писали:

Я понимаю, что делает программа. Мне непонятно, зачем разработчик берет адрес указателя преобразует его в Node и от него ссылается на лругие объекты. Не наглядно. Во-вторых — где написано, что данные класса располагаются в памяти в той же последовательности? А если я переставлю местами, например, в классе List head и tail.
Re[3]: Поясните кусочек кода
От: Андрей Тарасевич Беларусь  
Дата: 01.09.03 15:03
Оценка:
Здравствуйте, jd-Kevin, Вы писали:

JK>Я понимаю, что делает программа. Мне непонятно, зачем разработчик берет адрес указателя преобразует его в Node и от него ссылается на лругие объекты. Не наглядно. Во-вторых — где написано, что данные класса располагаются в памяти в той же последовательности? А если я переставлю местами, например, в классе List head и tail.


Все рухнет. Автор кода пытается использовать заголовок списка ('List') в качестве элемента списка ('Node'), надеясь на совпадение layout-ов этих классов в памяти. Очень грубое и, в общем случае, неработоспособное решение. Пример того, как делать ни в коем случае нельзя.

Если уж автору так хотелось включить в цикл и сам заголовок списка, то можно было бы сделать один из 'Node' членом 'List'.
Best regards,
Андрей Тарасевич
Re[3]: Поясните кусочек кода
От: Кодт Россия  
Дата: 01.09.03 15:15
Оценка:
Здравствуйте, 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()); }
};
Перекуём баги на фичи!
Re: Поясните кусочек кода
От: Анатолий Широков СССР  
Дата: 01.09.03 15:17
Оценка:
Посмотрим на объекта класса List, что называется, в разрезе:

0 1 2 3 4 5 6 7 8 9 0 1 2
|  head |  tail |tailpred|


Причем, после начальной инициализации List имеет следующее состояние

    +--->---+
|   *   |   0   |    *   |
    +-------<--------+ 

где 
*-->-- указатель


Теперь наложим структуру узла:

        | succ  |  pred  |  prt   |


И увидим, что не вводя явно фиктивного концевика, разработчик его все-таки получил

Но, так как нет никаких гарантий (во всяком случае, они в коде не прозвучали явно) относительно смещений членов в структурах List и Node, то можно считать, что мы наблюдали грязный хак Но, все равно, код заслуживает внимания
Re[2]: Поясните кусочек кода
От: Кодт Россия  
Дата: 01.09.03 15:30
Оценка:
Здравствуйте, Анатолий Широков, Вы писали:

АШ>Но, так как нет никаких гарантий (во всяком случае, они в коде не прозвучали явно) относительно смещений членов в структурах List и Node, то можно считать, что мы наблюдали грязный хак Но, все равно, код заслуживает внимания


А чтобы хак был чище — нужно завести структуру из двух указателей, Node и List унаследовать от нее, и делать static_cast (не reinterpret_cast !!!) к нужному типу.
Перекуём баги на фичи!
Re[3]: Поясните кусочек кода
От: Анатолий Широков СССР  
Дата: 01.09.03 15:36
Оценка:
К>А чтобы хак был чище — нужно завести структуру из двух указателей, Node и List унаследовать от нее, и делать static_cast (не reinterpret_cast !!!) к нужному типу.

Дядя Кодт, а я что? Я ничего

Я бы вообще малой кровью обошелся:


class List
{
public:
   List()
   {
      head = &fiction;
      fiction.succ = 0;  
      fiction.pred = head;
   }
   ...
pivate:
   Node* head;
   Node fiction;
};
Re[4]: Поясните кусочек кода
От: Кодт Россия  
Дата: 01.09.03 15:59
Оценка: 11 (3)
Здравствуйте, Анатолий Широков, Вы писали:

АШ>Я бы вообще малой кровью обошелся:

АШ>
АШ>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 узел-заглушку (который сам на себя указывает)
    + быстрая и бескровная диагностика
    — единожды попав в этот узел, невозможно вернуться назад

  • левая и правая заглушки (для каждого списка — свои)
    + попав в заглушку, можно пойти назад; попытка идти дальше — оказываешься на этой же заглушке
    — два лишних экземпляра данных

  • первый и последний элементы — являются заглушками, но несут данные (соответственно, они размещены на куче)
    + ничего лишнего
    — усложняется управление списком

  • универсальная заглушка (список свернут в кольцо с одним обязательным фиктивным элементом)
    + компактность
    — попав в заглушку, можно перескочить и оказаться в голове (хвосте) списка
  • Перекуём баги на фичи!
    Re[5]: Поясните кусочек кода
    От: Анатолий Широков СССР  
    Дата: 01.09.03 16:06
    Оценка:
    К>Вообще, двусвязный список можно организовать несколькими способами:
    К>...

    "Внушает" (с)

    Извини, больше поставить не могу — деградирую
    Re[4]: Поясните кусочек кода
    От: Денис Рубцов Россия  
    Дата: 04.09.03 12:53
    Оценка: 26 (1)
    Здравствуйте, Андрей Тарасевич!

    Большое спасибо, Андрей (и остальные) за разъяснения.
    А мои слова в адрес "некоторых форумов" я беру обратно. Поспешил, однако.
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.