Приклад використання shared_ptr?


82

Привіт, сьогодні я задав питання про те, як вставити різні типи об’єктів в один і той же векторний масив, і мій код у цьому питанні був

 gate* G[1000];
G[0] = new ANDgate() ;
G[1] = new ORgate;
//gate is a class inherited by ANDgate and ORgate classes
class gate
{
 .....
 ......
 virtual void Run()
   {   //A virtual function
   }
};
class ANDgate :public gate 
  {.....
   .......
   void Run()
   {
    //AND version of Run
   }  

};
 class ORgate :public gate 
  {.....
   .......
   void Run()
   {
    //OR version of Run
   }  

};      
//Running the simulator using overloading concept
 for(...;...;..)
 {
  G[i]->Run() ;  //will run perfectly the right Run for the right Gate type
 } 

і я хотів використовувати вектори, щоб хтось написав, що я повинен це зробити:

std::vector<gate*> G;
G.push_back(new ANDgate); 
G.push_back(new ORgate);
for(unsigned i=0;i<G.size();++i)
{
  G[i]->Run();
}

але тоді він та багато інших запропонували мені краще використовувати контейнери покажчика Boost
або shared_ptr. Я провів останні 3 години читання на цю тему, але документація мені здається досить просунутою. **** Чи може хто-небудь дати мені невеликий приклад shared_ptrвикористання коду та чому він запропонував використовувати shared_ptr. Крім того, існують і інші типи , такі як ptr_vector, ptr_listі ptr_deque** **

Edit1: Я теж прочитав приклад коду, який включав:

typedef boost::shared_ptr<Foo> FooPtr;
.......
int main()
{
  std::vector<FooPtr>         foo_vector;
........
FooPtr foo_ptr( new Foo( 2 ) );
  foo_vector.push_back( foo_ptr );
...........
}

І я не розумію синтаксис!


2
Який синтаксис ви не розумієте? Перший рядок mainстворює вектор, який може містити спільні вказівники на тип, який називається Foo; другий створює Fooвикористання newта спільний вказівник для управління ним; третій поміщає копію спільного вказівника у вектор.
Mike Seymour

Відповіді:


116

Використання vectorв shared_ptrвиключає можливість витоку пам'яті , тому що ви забули йти вектор і виклик deleteпо кожному елементу. Давайте пройдемося по трохи модифікованій версії прикладу, за рядком.

typedef boost::shared_ptr<gate> gate_ptr;

Створіть псевдонім для спільного типу вказівника. Це дозволяє уникнути потворності в мові С ++, що виникає внаслідок набору тексту std::vector<boost::shared_ptr<gate> >та забуття пробілу між закриваючими знаками більше, ніж .

    std::vector<gate_ptr> vec;

Створює порожній вектор boost::shared_ptr<gate>об’єктів.

    gate_ptr ptr(new ANDgate);

Виділіть новий ANDgateекземпляр і збережіть його в shared_ptr. Причиною цього є окреме запобігання проблемі, яка може виникнути, якщо операція викине. У цьому прикладі це неможливо. У Boost shared_ptr"Best Practices" пояснюється, чому найкращою практикою є розміщення у окремо стоячому об’єкті, а не в тимчасовому.

    vec.push_back(ptr);

Це створює новий спільний вказівник у векторі та копіює ptrв нього. Підрахунок посилань у кишках shared_ptrгарантує ptrбезпечне перенесення виділеного об'єкта всередину у вектор.

Що не пояснюється, так це те, що деструктор для shared_ptr<gate>гарантує видалення виділеної пам'яті. Тут уникнути витоку пам’яті. Деструктор for std::vector<T>гарантує, що деструктор for Tвикликається для кожного елемента, що зберігається у векторі. Однак деструктор для вказівника (наприклад, gate*) не видаляє виділену вами пам'ять . Цього ви намагаєтеся уникнути, використовуючи shared_ptrабо ptr_vector.


1
Це було докладно :). Моє запитання стосується 3-го рядка коду gate_ptr ptr (новий ANDgate); Мені це не здається досить знайомим, ptr загального типу вказівника, а потім між фігурними дужками ви надіслали новий ANDgate! Це бентежить.
Ахмед

6
@Ahmed: загальний вираз є ініціалізацією змінної, подібно до того, як ви можете написати int x(5);ініціалізацію xзі значенням 5. У цьому випадку він ініціалізується значенням нового виразу, який створює ANDgate; значення new-expression є покажчиком на новий об'єкт.
Майк Сеймур

42

Я додам , що одна з важливих речей , про shared_ptr«s це тільки коли - небудь побудувати їх з наступним синтаксисом:

shared_ptr<Type>(new Type(...));

Таким чином, "справжній" вказівник на Typeанонімний для вашої області дії і утримується лише спільним вказівником. Таким чином, вам буде неможливо випадково використовувати цей "справжній" покажчик. Іншими словами, ніколи не робіть цього:

Type* t_ptr = new Type(...);
shared_ptr<Type> t_sptr ptrT(t_ptr);
//t_ptr is still hanging around!  Don't use it!

Хоча це буде працювати, тепер у вашій функції є Type*покажчик ( t_ptr), який живе поза загальним покажчиком. Небезпечно використовувати t_ptrде-небудь, тому що ви ніколи не знаєте, коли спільний покажчик, який утримує його, може його зруйнувати, і ви здійсните сегментацію.

Те саме стосується покажчиків, повернутих вам іншими класами. Якщо клас, який ви не написали, передає вам вказівник, як правило, не безпечно просто помістити його в shared_ptr. Тільки якщо ви не впевнені, що клас більше не використовує цей об’єкт. Тому що якщо ви все-таки помістите його в a shared_ptr, і воно вийде за межі сфери дії, об’єкт звільниться, коли клас все ще може знадобитися.


8
Все, що сказав Кен, є добрим і правдивим, але я вважаю, що найкращим способом називати це зараз є auto t_ptr = make_shared<Type>(...);або еквівалентно shared_ptr<Type> t_ptr = make_shared<Type>(...);, просто тому, що така форма є більш ефективною.
Джейсон Сайдес,

@KenSimon, чи повинна бути кома ,між t_sptrі ptrTвсередині shared_ptr<Type> t_sptr ptrT(t_ptr);?
Allanqunzi

Окрім неоднозначностей у прикладі коду, гарне попередження - але сором, що ви повинні це зробити, оскільки 1-ша форма набагато чистіша, і, що ще важливіше, напевно, кожен, хто використовує розумний вказівник, знає, що він існує саме для того, щоб уникнути небезпечної сировини покажчики, що плавають навколо. Останній абзац цікавий; на щастя, я ще не працював з жодною бібліотекою, яка змушує мене використовувати точки необробленого або незрозумілого типу, хоча я впевнений, що це відбудеться деякий час.
underscore_d

20

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

Проблема, яка виникає, полягає в тому, що за винятками може бути дуже важко переконатися, що об’єкт завжди випускається лише один раз на всіх можливих шляхах виконання.

Це причина для RAII: http://en.wikipedia.org/wiki/RAII

Створення допоміжного класу з метою переконатися, що об’єкт завжди видаляється один раз у всіх шляхах виконання.

Приклад такого класу: std :: auto_ptr

Але іноді вам подобається ділитися предметами з іншими. Його слід видаляти лише тоді, коли його більше ніхто не використовує.

Для того, щоб допомогти в цьому, були розроблені стратегії підрахунку посилань, але вам все одно потрібно пам'ятати addref та випустити ref вручну. По суті, це та сама проблема, що і new / delete.

Ось чому boost розробив boost :: shared_ptr, це підрахунок посилань на розумний вказівник, щоб ви могли спільно використовувати об'єкти і ненавмисно витікати з пам'яті.

З додаванням C ++ tr1 це тепер також додано до стандарту c ++, але його ім'я std :: tr1 :: shared_ptr <>.

Я рекомендую використовувати стандартний спільний вказівник, якщо це можливо. ptr_list, ptr_dequeue тощо - це спеціалізовані контейнери IIRC для типів покажчиків. Наразі я їх ігнорую.

Тож ми можемо почати з вашого прикладу:

std::vector<gate*> G; 
G.push_back(new ANDgate);  
G.push_back(new ORgate); 
for(unsigned i=0;i<G.size();++i) 
{ 
  G[i]->Run(); 
} 

Зараз проблема полягає в тому, що кожного разу, коли G виходить із зони дії, ми витікаємо 2 об’єкти, додані до G. Давайте перепишемо його на використання std :: tr1 :: shared_ptr

// Remember to include <memory> for shared_ptr
// First do an alias for std::tr1::shared_ptr<gate> so we don't have to 
// type that in every place. Call it gate_ptr. This is what typedef does.
typedef std::tr1::shared_ptr<gate> gate_ptr;    
// gate_ptr is now our "smart" pointer. So let's make a vector out of it.
std::vector<gate_ptr> G; 
// these smart_ptrs can't be implicitly created from gate* we have to be explicit about it
// gate_ptr (new ANDgate), it's a good thing:
G.push_back(gate_ptr (new ANDgate));  
G.push_back(gate_ptr (new ORgate)); 
for(unsigned i=0;i<G.size();++i) 
{ 
   G[i]->Run(); 
} 

Коли G виходить із зони дії, пам'ять автоматично відновлюється.

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


Мій інструктор дав мені подібну пораду щодо написання власних занять, тому я спробую це напевно. TY.
Ахмед

вам слід використовувати ітератор, щоб запустити всі воротаfor( auto itt = G.begin(); itt != G.end(); ++itt ){ itt->Run(); }
Гійом Массе

1
Або ще краще новий "foreach" в C ++
Просто черговий метапрограміст

2

Документація про підсилення містить досить хороший приклад запуску: приклад shared_ptr (насправді йдеться про вектор розумних вказівників) або shared_ptr doc . Наступна відповідь Йоганнеса Шауба досить добре пояснює прискорення розумних вказівників: пояснили розумні вказівники

Ідея (якомога менше слів) ptr_vector полягає в тому, що він обробляє для вас вивільнення пам'яті за збереженими вказівниками: припустимо, у вас є вектор вказівників, як у вашому прикладі. Коли ви виходите з програми або залишаєте область, у якій визначений вектор, вам доведеться прибирати за собою (ви динамічно розподіляли ANDgate та ORgate), але просто очищення вектора цього не зробить, оскільки вектор зберігає вказівники а не фактичні об'єкти (він не знищить, а те, що він містить).

 // if you just do
 G.clear() // will clear the vector but you'll be left with 2 memory leaks
 ...
// to properly clean the vector and the objects behind it
for (std::vector<gate*>::iterator it = G.begin(); it != G.end(); it++)
{
  delete (*it);
}

boost :: ptr_vector <> обробить вищезазначене для вас - це означає, що він звільнить пам’ять за вказівниками, які він зберігає.


shared_ptr - це розумний вказівник - блискуча "обгортка" для звичайного вказівника, який, скажімо, додає деякий ШІ до типу вказівника. ptr_vector - це розумний контейнер для покажчиків - "обгортка" для контейнера покажчиків.
celavek

отже ptr_vector є своєрідною заміною нормального вектора?
Ахмед

@Ahmed Думаю, ти можеш про це подумати так.
celavek

2

Через Boost ви можете це зробити>

std::vector<boost::any> vecobj;
    boost::shared_ptr<string> sharedString1(new string("abcdxyz!"));    
    boost::shared_ptr<int> sharedint1(new int(10));
    vecobj.push_back(sharedString1);
    vecobj.push_back(sharedint1);

> для вставки іншого типу об'єкта у ваш векторний контейнер. в той час як для доступу ви повинні використовувати any_cast, який працює як dynamic_cast, сподіваємось, він буде працювати для ваших потреб.


1
#include <memory>
#include <iostream>

class SharedMemory {
    public: 
        SharedMemory(int* x):_capture(x){}
        int* get() { return (_capture.get()); }
    protected:
        std::shared_ptr<int> _capture;
};

int main(int , char**){
    SharedMemory *_obj1= new SharedMemory(new int(10));
    SharedMemory *_obj2 = new SharedMemory(*_obj1);
    std::cout << " _obj1: " << *_obj1->get() << " _obj2: " << *_obj2->get()
    << std::endl;
    delete _obj2;

    std::cout << " _obj1: " << *_obj1->get() << std::endl;
    delete _obj1;
    std::cout << " done " << std::endl;
}

Це приклад shared_ptr в дії. _obj2 видалено, але покажчик все ще діє. вихід, ./test _obj1: 10 _obj2: 10 _obj2: 10 виконано


0

Найкращий спосіб додавати різні об'єкти в один контейнер - це використовувати цикл make_shared, vector та range, і ви отримаєте приємний, чистий і "читабельний" код!

typedef std::shared_ptr<gate> Ptr   
vector<Ptr> myConatiner; 
auto andGate = std::make_shared<ANDgate>();
myConatiner.push_back(andGate );
auto orGate= std::make_shared<ORgate>();
myConatiner.push_back(orGate);

for (auto& element : myConatiner)
    element->run();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.