Розумні вказівники

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до: навігація, пошук

В комп’ютерних науках розумний вказівник (Smart pointer), це абстрактний тип даних, який імітує вказівник з допоміжними можливостями, такими як автоматичне управління пам’яттю або перевірку виходу за межі виділеної пам'яті. Ці додаткові можливості направлені на те щоб зменшити кількість програмних помилок, які виникають при зловживаннях при роботі з вказівниками, зберігаючи ефективність. Розумні вказівники зазвичай слідкують за пам'яттю, на яку вони вказують. Вони також можуть використовуватись, щоб впорядковувати інші ресурси, такі як мережеві з’єднання і файлові дескриптори.

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

Існує декілька типів розумних вказівників. Деякі працюють на основі підрахунку посилань, інші шляхом привласнення об'єкта одному єдиному вказівнику. Якщо для мови програмування існує підтримка збирача сміття (наприклад, Java або C#), тоді розумні вказівники не є потрібними для управління пам'яттю, але можуть використовуватися для управління ресурсами, такими як файлові дескриптори чи мережеві з’єднання.

Розумні вказівники в C++[ред.ред. код]

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

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

some_type* ambiguous_function(); // Що треба робити з результатом?

Зазвичай це вирішується додаванням коментарів, але це може призвести до помилки. Потенційні помилки, які можуть бути викликані такою ситуацією будуть зустрічатися рідше, якщо звичайний вказівник у результаті функції замінити на unique_ptr:

unique_ptr<some_type> obvious_function1();

Функція робить явним те, що користувач функції має розпоряджатися результатом. Крім того, ніякого витоку пам'яті не відбудеться, якщо програміст не зробить ніякої дії. У версіях до появи стандарту C++11, unique_ptr можна замінити на auto_ptr.

unique_ptr[ред.ред. код]

C++11 забезпечує шаблонний клас std::unique_ptr, оголошений в файлі заголовку <memory>.[1]

Конструктор копій і оператор присвоювання класу std::auto_ptr не виконують фактичне копіювання вказівника, який зберігає клас. Замість того, вони передають його значення, залишаючи первісний об'єкт std::auto_ptr порожнім. Це один із способів реалізації правила чіткого володіння, при якому лише один екземпляр std::auto_ptr може володіти вказівником в конкретний момент часу. Таким чином, std::auto_ptr не повинен використовуватись у коді, де необхідно мати семантику копіювання. [2]

C++11 має підтримку семантики переміщення; яка дозволяє явно здійснити переміщення значень у вигляді окремої операції, на відміну від операції копіювання. Також C++11 дозволяє явно захистити об'єкт від копіювання. Оскільки std::auto_ptr уже був створений із його семантикою копіювання, її не можна змінити без порушення зворотної сумісності коду. Для цих цілей, в C++11 запропонований новий тип вказівника std::unique_ptr.

Цей вказівник має свій конструктор копій, а оператор присвоювання явно не доступний; він не може бути скопійований. Він може бути переміщений функцією std::move, який дозволяє вказівнику unique_ptr передати у власність об'єкт іншому вказівнику.

std::unique_ptr<int> p1(new int(5));
std::unique_ptr<int> p2 = p1; //Помилка компіляції
std::unique_ptr<int> p3 = std::move(p1); //Передає об'єкт. p3 тепер є власником пам'яті, а p1 став не валідним.
 
p3.reset(); //Вивільняє пам'ять
p1.reset(); //Не робить нічого.

Клас std::auto_ptr є досі доступним, але він відмічений як застарілий у версії стандарту C++11.

shared_ptr і weak_ptr[ред.ред. код]

C++11 включає в себе класи shared_ptr і weak_ptr, які основані на аналогічних версіях, створених у бібліотеці Boost. TR1 запропонував їх як стандарт, але в C++11 до них додали додаткового функціоналу в порівнянні з Boost.

Клас std::shared_ptr підраховує посилання на об'єкт, власником якого він є. Кожна копія одного shared_ptr містить однаковий вказівник. Пам'ять за тим вказівником буде звільнена, лише коли всі екземпляри shared_ptr в програмі будуть видалені.

std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; //Обидва вказують на одну область пам'яті.
 
p1.reset(); //Дані в пам'яті зберігаються, завдяки існуванню p2.
p2.reset(); //Вивільняє пам'ять, оскільки більше ніхто не посилається на неї.

Клас std::shared_ptr використовує підрахунок посилань, тому потенційною проблемою для таких вказівників є циклічні посилання. Щоб уникнути виникнення таких циклів, для доступу до таких об'єктів можна користуватися класом std::weak_ptr. Збережений об'єкт буде видалений лише у випадку коли власниками об'єкту є екземпляри weak_ptr. weak_ptr таким чином не дає гарантії, що об'єкт продовжить існувати, але він може здійснити запит до ресурсу.

std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; //p1 є власником об'єкта в пам'яті.
{
  std::shared_ptr<int> p2 = wp1.lock(); //тепер p1 і p2 є власниками пам'яті.
  if(p2) // оскільки p2 був визначений як слабкий вказівник, вам слід перевірити чи існує досі об'єкт в пам'яті!
  { 
    //Дії з p2
  }
} //p2 видалений. Посилання на пам'ять міститься в p1.
 
p1.reset(); //Пам'ять буде видалена.
 
std::shared_ptr<int> p3 = wp1.lock(); //Об'єкт не існує, тому ми отримаємо порожній shared_ptr.
if(p3)
{
  //Не виконає цього коду.
}

Примітки[ред.ред. код]

  1. ISO 14882:2011 20.7.1
  2. CERT C++ Secure Coding Standard 08. Memory Management (MEM)

Література[ред.ред. код]

  1. «С++ for real programmers», Джефф Елджер