Що таке розумний покажчик і коли його слід використовувати?
Що таке розумний покажчик і коли його слід використовувати?
Відповіді:
ОНОВЛЕННЯ
Ця відповідь досить стара, і так описується те, що було «добре» в той час, які розумні вказівки надавали бібліотека Boost. Так як C ++ 11, стандартна бібліотека надала достатні типи смарт - покажчики, і тому ви повинні сприяти використанню std::unique_ptr
, std::shared_ptr
і std::weak_ptr
.
Був також std::auto_ptr
. Це дуже нагадувало масштабний покажчик, за винятком того, що він також мав "особливу" небезпечну здатність копіювати - що також несподівано передає право власності.
Він був застарілим у C ++ 11 та видалений у C ++ 17 , тому не слід його використовувати.
std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership.
// p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.
СТАРИЙ ВІДПОВІДЬ
Розумний вказівник - це клас, який обертає "сирий" (або "голий") покажчик C ++, щоб керувати терміном експлуатації об'єкта, на який вказують. Єдиного типу інтелектуального вказівника не існує, але всі вони намагаються практичним способом абстрагувати необроблений покажчик.
Інтелектуальні покажчики слід віддавати перевагу над необробленими покажчиками. Якщо вам здається, що вам потрібно використовувати вказівники (спершу подумайте, чи справді ви це робите), ви зазвичай хочете скористатися розумним вказівником, оскільки це може полегшити багато проблем із необробленими вказівниками, в основному забувши видалити об'єкт і витік пам'яті.
За допомогою необроблених покажчиків програміст повинен явно знищити об'єкт, коли він більше не корисний.
// Need to create the object to achieve some goal
MyObject* ptr = new MyObject();
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?
Розумний вказівник для порівняння визначає політику щодо знищення об'єкта. Вам все одно доведеться створити об’єкт, але вам більше не доведеться турбуватися про його знищення.
SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.
// Destruction of the object happens, depending
// on the policy the smart pointer class uses.
// Destruction would happen even if DoSomething()
// raises an exception
Найпростіша застосовувана політика передбачає сферу використання об'єкта для обгортки інтелектуального вказівника, такого як реалізований boost::scoped_ptr
або std::unique_ptr
.
void f()
{
{
std::unique_ptr<MyObject> ptr(new MyObject());
ptr->DoSomethingUseful();
} // ptr goes out of scope --
// the MyObject is automatically destroyed.
// ptr->Oops(); // Compile error: "ptr" not defined
// since it is no longer in scope.
}
Зауважте, що std::unique_ptr
екземпляри неможливо скопіювати. Це запобігає видаленню вказівника кілька разів (неправильно). Однак ви можете передавати посилання на інші функції, які ви викликаєте.
std::unique_ptr
s корисні, коли ви хочете прив’язати термін експлуатації об'єкта до певного блоку коду, або якщо ви вбудували його як дані члена всередині іншого об'єкта, термін служби цього іншого об'єкта. Об'єкт існує до тих пір, поки не містить блоку коду або до його знищення.
Більш складна політика інтелектуального вказівника передбачає підрахунок посилань. Це дозволяє скопіювати покажчик. Коли остання "посилання" на об'єкт знищена, об'єкт видаляється. Цю політику реалізує boost::shared_ptr
та std::shared_ptr
.
void f()
{
typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
MyObjectPtr p1; // Empty
{
MyObjectPtr p2(new MyObject());
// There is now one "reference" to the created object
p1 = p2; // Copy the pointer.
// There are now two references to the object.
} // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero.
// The object is deleted.
Довідкові підрахунки покажчиків дуже корисні, коли термін експлуатації вашого об'єкта значно складніший і не прив’язаний безпосередньо до певного розділу коду чи іншого об’єкта.
Є один недолік посилальних підрахунків покажчиків - можливість створення звисаючої посилання:
// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!
Ще одна можливість створення кругових посилань:
struct Owner {
std::shared_ptr<Owner> other;
};
std::shared_ptr<Owner> p1 (new Owner());
std::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1
// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!
Щоб вирішити цю проблему, і Boost, і C ++ 11 визначили a, weak_ptr
щоб визначити слабке (незліченне) посилання на a shared_ptr
.
std::auto_ptr<MyObject> p1 (new MyObject());
замість std::auto_ptr<MyObject> p1 (new Owner());
?
const std::auto_ptr
безпечний у використанні, якщо ви застрягли з C ++ 03. Я досить широко використовував його для шаблону pimpl, поки не отримав доступ до C ++ 11.
Ось простий варіант відповіді сучасних C ++ (C ++ 11 і пізніших версій):
std::unique_ptr
коли ви не маєте наміру містити кілька посилань на один і той же об’єкт. Наприклад, використовуйте його для вказівника на пам'ять, який виділяється при введенні деякої області та де-розподілений при виході з області дії.std::shared_ptr
коли ви хочете посилатись на ваш об'єкт з декількох місць - і не хочете, щоб ваш об’єкт не був виділений, поки всі ці посилання самі не зникнуть.std::weak_ptr
коли ви хочете посилатись на ваш об'єкт з декількох місць - для тих посилань, для яких нормально ігнорувати та розміщувати місця (так що вони просто зауважать, що об'єкт відсутній, коли ви намагаєтесь знешкодити).boost::
смарт-покажчики, std::auto_ptr
за винятком випадків, коли це потрібно прочитати.T*
- це std::unique_ptr<T>
що std::weak_ptr<T>
робитиstd::shared_ptr<T>
Розумний вказівник - це тип, що нагадує вказівник, з деякими додатковими функціональними можливостями, наприклад, автоматичним розміщенням пам'яті, підрахунком посилань тощо.
Невеликий вступ доступний на сторінці Smart покажчики - що, навіщо, що? .
Одним із простих типів смарт-покажчика є std::auto_ptr
(глава 20.4.5 стандарту C ++), який дозволяє автоматично розміщувати пам'ять, коли вона виходить за межі, і яка є більш надійною, ніж просто використання покажчика, коли викиди викидів, хоча і менш гнучкі.
Ще одним зручним типом є те, boost::shared_ptr
що реалізує підрахунок посилань і автоматично передає пам'ять, коли не залишається посилання на об'єкт. Це допомагає уникнути витоку пам'яті та простий у використанні для реалізації RAII .
Тема глибоко висвітлена у книзі "Шаблони C ++: Повний посібник" Девіда Вандевурде, Ніколая М. Йосуттіса , глава 20. Розумні покажчики. Деякі теми висвітлювались:
std::auto_ptr
застаріле і сильно перешкоджає, оскільки ви можете випадково передати право власності. - C ++ 11 знімає необхідність Boost, використання: std::unique_ptr
, std::shared_ptr
іstd::weak_ptr
Визначення, які дають Кріс, Сергдев та Ллойд, є правильними. Я віддаю перевагу більш простому визначенню, щоб просто зробити своє життя простим: Розумний покажчик - це просто клас, який перевантажує ->
і *
операторів. Це означає , що ваш об'єкт семантично виглядає як покажчик , але ви можете зробити це робити крутіше речі, включаючи підрахунок посилань, автоматичне знищення і т.д.
, shared_ptr
і auto_ptr
досить в більшості випадків, але приходьте разом зі своїм власним набором маленької ідіосинкразії.
Розумний вказівник - це як звичайний (набраний) вказівник, як "char *", за винятком випадків, коли сам вказівник виходить за межі, а також видаляється те, на що він вказує. Ви можете використовувати його як звичайний покажчик, використовуючи "->", але не, якщо вам потрібен фактичний вказівник на дані. Для цього можна використовувати "& * ptr".
Це корисно для:
Об'єкти, які повинні бути виділені новими, але які ви хочете мати такий самий термін експлуатації, як і щось у цій стеці. Якщо об'єкт призначений для інтелектуального вказівника, він буде видалений, коли програма закриє цю функцію / блок.
Дані членів класів, так що при видаленні об'єкта всі дані, що належать, також видаляються без спеціального коду в деструкторі (вам потрібно буде бути впевненим, що деструктор віртуальний, що майже завжди добре робити) .
Можливо, ви не хочете використовувати смарт-покажчик, коли:
Дивись також:
Більшість видів розумних покажчиків обробляють розпорядження вказівником для вас. Це дуже зручно, оскільки вам більше не потрібно думати про утилізацію предметів вручну.
Інтелектуальні покажчики, що найчастіше використовуються, - це std::tr1::shared_ptr
(або boost::shared_ptr
), а рідше - std::auto_ptr
. Я рекомендую регулярне використання shared_ptr
.
shared_ptr
дуже універсальний і стосується великої кількості різноманітних сценаріїв розпорядження, включаючи випадки, коли об’єкти потрібно "передавати через межі DLL" (звичайний випадок кошмару, якщо libc
між вашим кодом та DLL використовуються різні s).
Розумний вказівник - це об'єкт, який діє як вказівник, але додатково забезпечує контроль над побудовою, знищенням, копіюванням, переміщенням та перенаправленням.
Можна реалізувати власний інтелектуальний вказівник, але багато бібліотек також забезпечують реалізацію розумних покажчиків, кожна з яких має різні переваги та недоліки.
Наприклад, Boost забезпечує такі реалізації інтелектуального вказівника:
shared_ptr<T>
є вказівником на T
використання опорного підрахунку для визначення, коли об'єкт більше не потрібен.scoped_ptr<T>
- покажчик автоматично видаляється, коли він виходить за межі області. Призначення неможливо.intrusive_ptr<T>
є ще одним покажчиком підрахунку опор. Він забезпечує більш високу продуктивність, ніж shared_ptr
вимагає, але тип T
повинен забезпечити власний механізм підрахунку опор.weak_ptr<T>
є слабким покажчиком, який працює в поєднанні з тим, shared_ptr
щоб уникнути кругових посилань.shared_array<T>
як shared_ptr
, але для масивів T
.scoped_array<T>
як scoped_ptr
, але для масивів T
.Це лише один лінійний опис кожного з них і може бути використаний у міру потреби, для подальшої деталізації та прикладів можна переглянути документацію Boost.
Крім того, стандартна бібліотека C ++ забезпечує три розумні покажчики; std::unique_ptr
за унікальну власність, std::shared_ptr
для спільної власності та std::weak_ptr
. std::auto_ptr
існувало в C ++ 03, але зараз застаріле.
scoped_ptr
це не так, як оголошено на місцях const unique_ptr
- яке також видаляється при виході з області дії.
Ось посилання на подібні відповіді: http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html
Розумний вказівник - це об'єкт, який діє, виглядає і виглядає як звичайний покажчик, але пропонує більше функціональних можливостей. У C ++ інтелектуальні покажчики реалізовані як шаблонні класи, які інкапсулюють покажчик та переосмислюють стандартні оператори вказівників. Вони мають ряд переваг перед звичайними покажчиками. Вони гарантовано ініціалізуються як нульові покажчики або покажчики на купу об'єкта. Перевіряється непрямий показник через нульовий покажчик. Видалення ніколи не потрібно. Об'єкти автоматично звільняються, коли останній вказівник на них відійшов. Важливою проблемою цих розумних покажчиків є те, що на відміну від звичайних покажчиків вони не поважають спадщину. Інтелектуальні покажчики непривабливі для поліморфного коду. Наведене нижче - приклад для здійснення інтелектуальних покажчиків.
Приклад:
template <class X>
class smart_pointer
{
public:
smart_pointer(); // makes a null pointer
smart_pointer(const X& x) // makes pointer to copy of x
X& operator *( );
const X& operator*( ) const;
X* operator->() const;
smart_pointer(const smart_pointer <X> &);
const smart_pointer <X> & operator =(const smart_pointer<X>&);
~smart_pointer();
private:
//...
};
Цей клас реалізує розумний вказівник на об’єкт типу X. Сам об’єкт розташований на купі. Ось як його використовувати:
smart_pointer <employee> p= employee("Harris",1333);
Як і інші перевантажені оператори, p поводитиметься як звичайний покажчик,
cout<<*p;
p->raise_salary(0.5);
http://en.wikipedia.org/wiki/Smart_pointer
В інформатиці розумний покажчик - це абстрактний тип даних, який імітує вказівник, надаючи додаткові функції, такі як автоматичне збирання сміття або перевірка меж. Ці додаткові функції призначені для зменшення помилок, викликаних неправильним використанням покажчиків, зберігаючи ефективність. Інтелектуальні покажчики зазвичай відслідковують об'єкти, які вказують на них з метою управління пам'яттю. Неправильне використання покажчиків є головним джерелом помилок: постійне розподілення, розміщення та посилання, яке повинно виконувати програма, написана за допомогою покажчиків, робить великою ймовірністю виникнення деяких витоків пам'яті. Розумні вказівники намагаються запобігти витоку пам’яті, зробивши розподіл ресурсів автоматичним: коли вказівник на об’єкт (або останній у ряді покажчиків) знищений,
Нехай Т є класом у цьому підручнику Покажчики на C ++ можна розділити на 3 типи:
1) Сирі покажчики :
T a;
T * _ptr = &a;
Вони зберігають адресу пам'яті до місця в пам'яті. Використовуйте обережно, оскільки програми стають складними, важко відстежувати.
Покажчики даних про const або адресу {Читайте назад}
T a ;
const T * ptr1 = &a ;
T const * ptr1 = &a ;
Вказівник на тип даних T, який є const. Значить, ви не можете змінити тип даних за допомогою вказівника. тобто *ptr1 = 19
; не вийде. Але ви можете перемістити вказівник. тобто ptr1++ , ptr1--
; і т.д. буде працювати. Читання назад: вказівник на тип T, який є const
T * const ptr2 ;
Вказівник const на тип даних T. Значить, ви не можете перемістити вказівник, але ви можете змінити значення, на яке вказує вказівник. тобто *ptr2 = 19
буде працювати, але ptr2++ ; ptr2--
і т.д. не буде працювати. Читання назад: вказівник const на тип T
const T * const ptr3 ;
Вказівник const на тип даних const T. Значить, ви не можете або перемістити вказівник, ні ви можете змінити вказівник типу даних, який буде вказівником. тобто. ptr3-- ; ptr3++ ; *ptr3 = 19;
не вийде
3) Розумні покажчики : { #include <memory>
}
Спільний вказівник :
T a ;
//shared_ptr<T> shptr(new T) ; not recommended but works
shared_ptr<T> shptr = make_shared<T>(); // faster + exception safe
std::cout << shptr.use_count() ; // 1 // gives the number of "
things " pointing to it.
T * temp = shptr.get(); // gives a pointer to object
// shared_pointer used like a regular pointer to call member functions
shptr->memFn();
(*shptr).memFn();
//
shptr.reset() ; // frees the object pointed to be the ptr
shptr = nullptr ; // frees the object
shptr = make_shared<T>() ; // frees the original object and points to new object
Реалізовано за допомогою підрахунку посилань, щоб відстежувати, скільки "речей" вказують на об'єкт, на який вказує вказівник. Коли цей підрахунок переходить до 0, об'єкт автоматично видаляється, тобто об’єкт видаляється, коли весь share_ptr, що вказує на об'єкт, виходить за межі області. Це позбавляється від головного болю від необхідності видалення об'єктів, які ви виділили за допомогою нових.
Слабкий вказівник: допомагає розібратися з циклічним посиланням, яке виникає при використанні Спільного вказівника Якщо у вас є два об'єкти, на які вказують два спільних вказівника, і є внутрішній загальний покажчик, що вказує один на одного спільним покажчиком, тоді буде циклічне посилання, і об'єкт не буде буде видалено, коли спільні покажчики виходять із сфери застосування. Щоб вирішити це, змініть внутрішній член з shared_ptr на slab_ptr. Примітка. Для доступу до елемента, на який вказує слабкий покажчик, використовуйте lock (), це повертає слабкий_ptr.
T a ;
shared_ptr<T> shr = make_shared<T>() ;
weak_ptr<T> wk = shr ; // initialize a weak_ptr from a shared_ptr
wk.lock()->memFn() ; // use lock to get a shared_ptr
// ^^^ Can lead to exception if the shared ptr has gone out of scope
if(!wk.expired()) wk.lock()->memFn() ;
// Check if shared ptr has gone out of scope before access
Дивіться: Коли корисний std :: слаб_ptr?
Унікальний покажчик: Легкий розумний покажчик з ексклюзивним правом власності. Використовуйте, коли вказівник вказує на унікальні об’єкти, не ділячись об'єктами між покажчиками.
unique_ptr<T> uptr(new T);
uptr->memFn();
//T * ptr = uptr.release(); // uptr becomes null and object is pointed to by ptr
uptr.reset() ; // deletes the object pointed to by uptr
Щоб змінити об'єкт, на який вказує унікальний ptr, використовуйте семантику переміщення
unique_ptr<T> uptr1(new T);
unique_ptr<T> uptr2(new T);
uptr2 = std::move(uptr1);
// object pointed by uptr2 is deleted and
// object pointed by uptr1 is pointed to by uptr2
// uptr1 becomes null
Посилання: Вони по суті можуть бути хоч як покажчиками const, тобто покажчиком, який є const і не може бути переміщений з кращим синтаксисом.
Див.: Які відмінності між змінною вказівника та еталонною змінною в C ++?
r-value reference : reference to a temporary object
l-value reference : reference to an object whose address can be obtained
const reference : reference to a data type which is const and cannot be modified
Довідка: https://www.youtube.com/channel/UCEOGtxYTB6vo6MQ-WQ9W_nQ Дякую Андре, що вказав на це питання.
Розумний покажчик - це клас, обгортка звичайного вказівника. На відміну від звичайних покажчиків, життєвий коло розумної точки базується на еталонному підрахунку (скільки часу присвоюється об'єкту інтелектуального вказівника). Отож, коли інтелектуальний покажчик присвоюється іншому, внутрішній відлік посилань плюс плюс. І кожного разу, коли об’єкт виходить за межі області, відлік посилань мінус мінус.
Автоматичний покажчик, хоч і схожий, абсолютно відрізняється від розумного вказівника. Це зручний клас, який передає ресурс кожного разу, коли автоматичний об'єкт вказівника виходить із змінної області. Певною мірою це змушує вказівник (на динамічно розподілену пам'ять) працює аналогічно змінній стеку (статично розподіляється під час компіляції).
Інтелектуальні покажчики - це ті, де вам не потрібно турбуватися про розподіл пам’яті, обмін ресурсами та передачу.
Ви можете дуже добре використовувати цей покажчик так само, як і будь-який розподіл працює на Java. У java Garbage Collector робить трюк, тоді як у Smart Pointers фокус роблять Destructors.
Існуючі відповіді хороші, але не висвітлюють, що робити, коли розумний покажчик не є (повною) відповіддю на проблему, яку ви намагаєтеся вирішити.
Крім усього іншого (що добре пояснено в інших відповідях), використання розумного вказівника є можливим рішенням Як використовувати абстрактний клас як тип повернення функції? що було позначено як дублікат цього питання. Однак першим питанням, яке потрібно задати, чи спокушається вказати абстрактний (або насправді будь-який) базовий клас як тип повернення в C ++, - це "що ти насправді маєш на увазі?". Існує хороша дискусія (з подальшими посиланнями) ідіоматичного об'єктно-орієнтованого програмування в C ++ (і як це відрізняється від інших мов) в документації бібліотеки контейнерів збільшити покажчик. Підсумовуючи, у C ++ ви повинні думати про право власності. Які розумні покажчики допомагають вам, але не є єдиним рішенням або завжди повноцінним рішенням (вони не дають вам поліморфної копії) і не завжди є рішенням, яке ви хочете викрити у своєму інтерфейсі (а повернення функції звучить жахливо багато як інтерфейс). Наприклад, може бути достатньо повернути посилання. Але у всіх цих випадках (розумний вказівник, контейнер вказівника або просто повернення посилання) ви змінили повернення зі значення на якусь форму посилання . Якщо вам дійсно потрібна копія, можливо, вам доведеться додати більше " idiom" на шаблоні або перейти за межі ідіоматичного (або іншим чином) OOP в C ++ до більш загального поліморфізму за допомогою бібліотек, таких як Adobe Poly або Boost.TypeErasure.