Тест на знання C++

Квітень 28th, 2009

Життя підкинуло цікаву ідею для перевірки практичних навичок C++ програміста. Таке собі тестове завдання.

Нижче наведено клас — зв’язаний список, призначений для зберігання всяких різних об’єктів. Клас поганий, нормальний С++ програміст такого собі дозволяти не повинен. Як ви гадаєте, що саме недобре у цьому класі?

  1. class Linked_Lists
  2. {
  3.     public:
  4.         Linked_Lists();
  5.         ~Linked_Lists();
  6.         bool add(void *data);
  7.         bool remove_head();
  8.         bool first();
  9.         bool last();
  10.         bool next();
  11.         bool prev();
  12.         void* get_data();
  13.         bool is_empty();
  14.         int get_count();
  15.  
  16.     protected:
  17.         struct Node
  18.         {
  19.             void *data;
  20.             Node *prev_node;
  21.             Node *next_node;
  22.         };
  23.         Node *head;
  24.         Node *tail;
  25.         Node *current;
  26.         int count;
  27. };

Слід сказати, тут дещо специфічний дизайн, так уже сталося. Елементи додаються та видаляються зі списку за принципом FIFO. Водночас, доступний вказівник, який можна туди-сюди пересувати по списку, щоб отримати доступ до будь-яких даних. Втім, це не суттєво. Припустимо, так було потрібно для чогось.

Справжній недолік цього класу в іншому. У якості підказки варто глянемо на реалізацію деструктора:

  1. Linked_Lists::~Linked_Lists()
  2. {
  3.     Node *node;
  4.  
  5.     if (head == 0) return;
  6.  
  7.     first();
  8.     do
  9.     {
  10.         node = current;
  11.         next();
  12.         delete node->data;
  13.         delete node;
  14.     } while (current != 0);
  15. }

Що ж саме у ньому не так?

Я не полінувався, зробив повну реалізацію цього класу і тестову програму. Все тут.

При компіляції можна отримати наступне попередження:

src/list.cpp:26: warning: deleting ‘void*’ is undefined

В тому то і полягає проблема. Операція delete відрізняється від C-шної функції free тим, що для при видаленні об’єктів додатково викликається деструктор. А випадку void* не відомо, який деструктор треба викликати (якщо взагалі треба). Тому програма, скомпільована g++ (за інші компілятори ручатися не буду) просто звільняє пам’ять, виділену під об’єкт, інші дії при цьому не виконуються.

У випадку використання стандартного типу (int який-небудь, або double) все буде нормально. І при використанні структур теж. А от якщо спробувати покласти до списку якийсь більш складний об’єкт, особливо зі зберіганням даних типу copy-on-write (QString, наприклад, або std::string), наслідки можуть бути абсолютно непередбачливі.

Виправити тут нічого не можна. Лише заново перепроектувати. Власне, спеціально для цього в C++ і було введено механізм шаблонів. Також можна виділити базовий клас і засовувати у список об’єкти його нащадків.

Написано за мотивами теми на одному форумі.

Категорії: C/C++ | Теґи:,

Залишити коментар