Автор оригинала: Babu_Abdulsalam Эта статья переведена сCodeProject, Пожалуйста, укажите источник.
представлять
Хотя в другой статье написаноC++11
умные указатели. В последнее время я слышал, как многие люди говорят оC++
Новый стандарт, так называемыйC++0x/C++11
. Я исследовалC++11
некоторые особенности языка, обнаружил, что в нем действительно есть огромные изменения. я сосредоточусь наC++11
раздел интеллектуального указателя.
задний план
Проблемы с обычными/сырыми/голыми указателями?
Давайте обсудим один за другим.
Указатели могут вызвать много проблем, если с ними не обращаться должным образом, поэтому люди всегда избегают их использования. Вот почему многие начинающие программисты не любят указатели. Указатели всегда вызывают массу проблем, таких как время жизни объекта, на который указывает указатель, зависание ссылок (dangling references
) и утечки памяти.
Отложенная ссылка возникает, когда на блок памяти ссылаются несколько указателей, но один из указателей освобожден, а остальные указатели не знают об этом. А утечки памяти, как известно, когда память выделяется из кучи и не освобождается обратно, тогда происходит утечка памяти. Некоторые люди говорят, что я написал чистый код с проверкой ошибок, зачем мне использовать умные указатели? Программист тоже спросил меня: "Эй, вот мой код, я его из кучи взял(heap
), и после его использования я правильно вернул его в кучу, так где же необходимость использовать умные указатели? "
void Foo( )
{
int* iPtr = new int[5];
//manipulate the memory block . . .
delete[ ] iPtr;
}
В идеале приведенный выше код будет работать нормально, и память будет должным образом освобождена. Но хорошенько подумайте о реальной рабочей среде и условиях выполнения кода. В промежутках между выделением и освобождением памяти программные инструкции действительно могут делать много плохих вещей, таких как доступ к недопустимым адресам памяти, деление на0
, или другой программист исправил ошибку в вашей программе, добавив оператор преждевременного возврата на основе условия.
Во всех вышеперечисленных случаях ваша программа не доходит до той части, где освобождается память. В первых двух случаях программа выдает исключение, а в третьем случае программа завершается преждевременно до освобождения памяти. Итак, когда программа запускается, происходит утечка памяти.
Решением всех вышеперечисленных проблем является использование интеллектуальных указателей [если они достаточно умны].
Что такое умный указатель?
Умный указатель — этоRAII
(Resource Acquisition is initialization
) модель класса, используемая для динамического выделения памяти. Он предоставляет все интерфейсы, предоставляемые обычными указателями, за очень немногими исключениями. При построении он выделяет память, а когда выходит за рамки, автоматически освобождает выделенную память. Таким образом, программисты освобождаются от утомительной задачи ручного управления динамической памятью.
C++98Предоставляется первый интеллектуальный указатель:auto_ptr
auto_ptr
давай познакомимсяauto_ptr
Как решить вышеуказанную проблему.
class Test
{
public:
Test(int a = 0 ) : m_a(a) { }
~Test( )
{
cout << "Calling destructor" << endl;
}
public: int m_a;
};
void main( )
{
std::auto_ptr<Test> p( new Test(5) );
cout << p->m_a << endl;
}
Приведенный выше код разумно освободит память, привязанную к указателю. Процесс действий следующий: подаем заявку на кусок памяти для храненияTest
объект и привязать его кauto_ptr
p
начальство. так когдаp
При выходе из области видимости блок памяти, на который она указывает, также автоматически освобождается.
//***************************************************************
class Test
{
public:
Test(int a = 0 ) : m_a(a)
{
}
~Test( )
{
cout<<"Calling destructor"<<endl;
}
public:
int m_a;
};
//***************************************************************
void Fun( )
{
int a = 0, b= 5, c;
if( a ==0 )
{
throw "Invalid divisor";
}
c = b/a;
return;
}
//***************************************************************
void main( )
{
try
{
std::auto_ptr<Test> p( new Test(5) );
Fun( );
cout<<p->m_a<<endl;
}
catch(...)
{
cout<<"Something has gone wrong"<<endl;
}
}
В приведенном выше примере указатель правильно освобождается, несмотря на то, что было выдано исключение. Это связано с тем, что при возникновении исключения стек освобождается (stack unwinding
),когдаtry
Уничтожив все объекты в блоке,p
Выход из области видимости, поэтому связанная с ней память также освобождается.
Проблема 1:
до этого момента,auto_ptr
Все еще достаточно умный, но у него все еще есть некоторые фундаментальные недостатки. при размещенииauto_ptr
назначить другомуauto_ptr
, его право собственности также передается. когда я прохожу между функциямиauto_ptr
, это проблема. Скажи, яFoo()
есть одинauto_ptr
, затем вFoo()
в я передал указатель наFun()
функционировать, когдаFun()
Когда функция завершит выполнение, право собственности на указатель не будет возвращеноFoo
.
//***************************************************************
class Test
{
public:
Test(int a = 0 ) : m_a(a)
{
}
~Test( )
{
cout<<"Calling destructor"<<endl;
}
public:
int m_a;
};
//***************************************************************
void Fun(auto_ptr<Test> p1 )
{
cout<<p1->m_a<<endl;
}
//***************************************************************
void main( )
{
std::auto_ptr<Test> p( new Test(5) );
Fun(p);
cout<<p->m_a<<endl;
}
из-заauto_ptr
поведение дикого указателя, приведенный выше код приводит к сбою программы. В этот период произошли эти детали,p
есть кусочек памяти, когдаFun
Когда звонили,p
Передать право собственности на связанный блок памяти auto_ptrp1
, p1
даp
копировать (примечание: здесь изFun
Определение функции показывает, что параметры функции передаются по значению, поэтому поместитеp
копируется в функцию), тоp1
прежде чем владетьp
собственный блок памяти. Все идет нормально. СейчасFun
После выполнения функцииp1
выходит за рамки, поэтомуp1
Соответствующий блок памяти также освобождается. Такp
Шерстяная ткань?p
Ничего не осталось, это причина сбоя, следующая строка кода все еще пытается получить доступp
,подобноp
Какие ресурсы у вас есть.
Проблема 2:
Есть еще один недостаток. auto_ptr не может указывать на набор объектов, то есть его нельзя использовать с оператором new[].
//***************************************************************
void main( )
{
std::auto_ptr<Test> p(new Test[5]);
}
Приведенный выше код вызовет ошибку времени выполнения. потому что когдаauto_ptr
Выходя за рамки,delete
Используется по умолчанию для освобождения связанного пространства памяти. когдаauto_ptr
Это, конечно, хорошо, когда мы указываем только на один объект, но в приведенном выше коде мы создаем набор объектов в куче, который следует использовать.delete[]
выпускать, а неdelete
.
Issue3:
auto_ptr
нельзя комбинировать со стандартными контейнерами (vector,list,map....) использовать вместе.
потому чтоauto_ptr
Склонен к ошибкам, поэтому он также будет объявлен устаревшим.C++11
Предоставляется новый набор интеллектуальных указателей, каждый из которых имеет свое назначение.
- shared_ptr
- unique_ptr
- weak_ptr
shared_ptr
Хорошо, подготовьтесь, чтобы насладиться настоящим интеллектом. Первый умный указательshared_ptr
, у него есть концепция, называемая долевой собственностью (sharedownership).shared_ptr
Цель очень проста: несколько указателей могут указывать на объект одновременно, когда последнийshared_ptr
Память автоматически освобождается, когда она покидает область видимости.
Создайте:
void main( )
{
shared_ptr<int> sptr1( new int );
}
использоватьmake_shared
макросы для ускорения процесса создания. потому чтоshared_ptr
Активно выделять память и вести подсчет ссылок (reference count
),make_shared
Создавайте работу более эффективным способом.
void main( )
{
shared_ptr<int> sptr1 = make_shared<int>(100);
}
Приведенный выше код создаетshared_ptr
, указывает на блок памяти, содержащий целое число100
и счетчик ссылок1
.если прошлоsptr1
создать еще одинshared_ptr
, счетчик ссылок становится равным 2. Этот счетчик называется强引用(strong reference)
,Помимо,shared_ptr
Существует еще один вид подсчета ссылок, называемый弱引用(weak reference)
, будут представлены позже.
позвонивuse_count()
Вы можете получить счетчик ссылок, из которого вы можете найтиshared_ptr
количество. При отладке можно наблюдатьshared_ptr
серединаstrong_ref
Значение является подсчетом ссылок.
уничтожить
shared_ptr
вызов по умолчаниюdelete
Освободите связанный ресурс. Если пользователь примет другую стратегию уничтожения, он может свободно указать конструкцию этойshared_ptr
стратегия. В следующем примере показана проблема, вызванная использованием стратегии уничтожения по умолчанию:
class Test
{
public:
Test(int a = 0 ) : m_a(a)
{
}
~Test( )
{
cout<<"Calling destructor"<<endl;
}
public:
int m_a;
};
void main( )
{
shared_ptr<Test> sptr1( new Test[5] );
}
В этом сценарииshared_ptr
указывает на массив объектов, но при выходе из области видимости вызывается деструктор по умолчаниюdelete
Освободите ресурсы. На самом деле, мы должны позвонитьdelete[]
уничтожить массив. Пользователь может сделать это, вызвав такую функцию, какlamda
выражение для указания общего шага выпуска.
void main( )
{
shared_ptr<Test> sptr1( new Test[5],
[ ](Test* p) { delete[ ] p; } );
}
Указав delete[] для уничтожения, приведенный выше код работает отлично.
интерфейсКак обычный указатель,shared_ptr
также предоставляет оператор разыменования*
,->
. Кроме того, он также предоставляет несколько более важных интерфейсов:
-
get()
: Получатьshared_ptr
связанный ресурс. -
reset()
: освободить владение связанным блоком памяти, если последний указывает на ресурсshared_ptr
, освободите эту память. -
unique
: определить, является ли он единственным, указывающим на текущую память.shared_ptr
. -
operator bool
: определить текущийshared_ptr
Указывает ли он на блок памяти, можно определить с помощью выражения if.
Хорошо, выше все оshared_ptr
описание, ноshared_ptr
Есть и некоторые проблемы:
Проблемы:
void main( )
{
shared_ptr<int> sptr1( new int );
shared_ptr<int> sptr2 = sptr1;
shared_ptr<int> sptr3;
sptr3 =sptr1
Issues:В следующей таблице показаны изменения счетчика ссылок в приведенном выше коде:
всеshared_ptrs
имеют одинаковый счетчик ссылок и принадлежат к одной и той же группе. Приведенный выше код работает нормально, давайте посмотрим на другой набор примеров.
void main( )
{
int* p = new int;
shared_ptr<int> sptr1( p);
shared_ptr<int> sptr2( p );
}
Приведенный выше код выдает ошибку, поскольку они принадлежат к разным группам.shared_ptr
указать на один и тот же ресурс. Следующая таблица дает представление о причине ошибки:
Чтобы избежать этой проблемы, старайтесь не начинать с голого указателя.(naked pointer)
Создайтеshared_ptr
.
class B;
class A
{
public:
A( ) : m_sptrB(nullptr) { };
~A( )
{
cout<<" A is destroyed"<<endl;
}
shared_ptr<B> m_sptrB;
};
class B
{
public:
B( ) : m_sptrA(nullptr) { };
~B( )
{
cout<<" B is destroyed"<<endl;
}
shared_ptr<A> m_sptrA;
};
//***********************************************************
void main( )
{
shared_ptr<B> sptrB( new B );
shared_ptr<A> sptrA( new A );
sptrB->m_sptrA = sptrA;
sptrA->m_sptrB = sptrB;
}
Приведенный выше код создает циклическую ссылку.A
правильноB
есть одинshared_ptr
, B
правильноA
есть также одинshared_ptr
,а такжеsptrA
а такжеsptrB
Связанные ресурсы не были освобождены, см. следующую таблицу:
sptrA
а такжеsptrB
Когда они выходят за рамки, их счетчики ссылок уменьшаются только до 1, поэтому ресурсы, на которые они указывают, не освобождаются! ! ! ! !
- если несколько
shared_ptrs
Блоки памяти, указанные как принадлежащие разным группам, вызовут ошибку. - Если вы создадите
shared_ptr
Возникает другая проблема. В приведенном выше коде, учитывая, что есть только одинshared_ptr
Кp
создан, код работает нормально. В случае, если программист удалит обычный указатель до того, как область интеллектуального указателя закончитсяp
. Боже мой! ! ! Еще одна авария. - Циклическая ссылка: если общий интеллектуальный указатель участвует в циклической ссылке, ресурс не будет освобожден в обычном режиме.
Чтобы разрешить циклические ссылки,C++
Предусмотрен другой тип интеллектуального указателя:weak_ptr
Weak_Ptr
weak_ptr
имеет общую семантику (sharing semantics
) и не содержит семантики (not owning semantics
). это означает,weak_ptr
можно поделитьсяshared_ptr
удерживаемые ресурсы. Таким образом, вы можете начать с ресурса, содержащегоshared_ptr
Создайтеweak_ptr
.
weak_ptr
Не поддерживает обычные указатели, содержащие*
,->
работать. Он не содержит ресурсов и поэтому не позволяет программистам манипулировать ресурсами. В таком случае, как мы используемweak_ptr
Шерстяная ткань?
Ответ отweak_ptr
создан вshared_ptr
Затем используйте его снова. За счет увеличения счетчика сильных ссылок ресурсы гарантированно не уничтожаются при использовании. Когда счетчик ссылок увеличивается, несомненно, что отweak_ptr
создан вshared_ptr
количество ссылок не менее1
.иначе, когда вы используетеweak_ptr
Могут возникнуть следующие проблемы: когдаshared_ptr
При выходе за рамки ресурсы, которыми он владеет, высвобождаются, что вызывает путаницу.
Создайте
возможноshared_ptr
Построить как параметрweak_ptr
.отshared_ptr
Создаватьweak_ptr
Увеличьте количество слабых ссылок общих указателей (weak reference
),иметь в видуshared_ptr
Распределение ресурсов, которым он владеет другими указателями. но когдаshared_ptr
При выходе из области действия этот счетчик не используется в качестве основы для высвобождения ресурсов. Другими словами, если сильный счетчик ссылок не становится0
, ресурс, на который указывает указатель, будет освобожден, здесь счетчик слабых ссылок (weak reference
) не работает.
void main( )
{
shared_ptr<Test> sptr( new Test );
weak_ptr<Test> wptr( sptr );
weak_ptr<Test> wptr1 = wptr;
}
Это видно из рисунка нижеshared_ptr
а такжеweak_ptr
Счетчик ссылок:
положитьweak_ptr
назначить другомуweak_ptr
увеличит количество слабых ссылок (weak reference count
).
Так когдаshared_ptr
При выходе из области действия ресурсы в ней высвобождаются, и это время указывает наshared_ptr
изweak_ptr
что случилось?weak_ptr
истекший(expired
).
как судитьweak_ptr
Чтобы указать на действительный ресурс, есть два способа:
- передача
use-count()
Чтобы получить счетчик ссылок, этот метод возвращает только счетчик сильных ссылок, а не счетчик слабых ссылок. - передача
expired()
метод. Чем звонитьuse_count()
метод быстрее.
отweak_ptr
передачаlock()
может получитьshared_ptr
или напрямуюweak_ptr
превратился вshared_ptr
void main( )
{
shared_ptr<Test> sptr( new Test );
weak_ptr<Test> wptr( sptr );
shared_ptr<Test> sptr2 = wptr.lock( );
}
Как упоминалось ранее, изweak_ptr
получено вshared_ptr
Увеличьте количество сильных ссылок.
Теперь давайте знакомитьсяweak_ptr
Как решить проблему циклической ссылки:
class B;
class A
{
public:
A( ) : m_a(5) { };
~A( )
{
cout<<" A is destroyed"<<endl;
}
void PrintSpB( );
weak_ptr<B> m_sptrB;
int m_a;
};
class B
{
public:
B( ) : m_b(10) { };
~B( )
{
cout<<" B is destroyed"<<endl;
}
weak_ptr<A> m_sptrA;
int m_b;
};
void A::PrintSpB( )
{
if( !m_sptrB.expired() )
{
cout<< m_sptrB.lock( )->m_b<<endl;
}
}
void main( )
{
shared_ptr<B> sptrB( new B );
shared_ptr<A> sptrA( new A );
sptrB->m_sptrA = sptrA;
sptrA->m_sptrB = sptrB;
sptrA->PrintSpB( );
}
unique_ptr
unique_ptr
даauto_ptr
замена.unique_ptr
Следует исключительной семантике. В любой момент времени ресурс может быть только уникальным.unique_ptr
владеть. когдаunique_ptr
Выходя за рамки, содержащиеся ресурсы высвобождаются. Если ресурс перезаписывается другим ресурсом, ранее принадлежавший ресурс будет освобожден. Таким образом, он гарантирует, что связанные с ним ресурсы всегда могут быть высвобождены.
Создайте
unique_ptr
как создать иshared_ptr
то же самое, если вы не создадите указатель на тип массиваunique_ptr
.
unique_ptr<int> uptr( new int );
unique_ptr
Предоставляет специальные методы для создания объектов массива, которые вызываются, когда указатель выходит за пределы области видимости.delete[]
заменятьdelete
. при созданииunique_ptr
, набор объектов считается частью параметра шаблона. Таким образом, программисту не нужно предоставлять указанный метод деструктора, как показано ниже:
unique_ptr<int[ ]> uptr( new int[5] );
когда положитьunique_ptr
При назначении другому объекту право собственности на ресурс передается.
запомнитьunique_ptr
Не обеспечивает семантику копирования (ни назначение копирования, ни построение копирования), поддерживает только семантику перемещения (move semantics
).
В приведенном выше примере, еслиupt3
а такжеupt5
Ресурс уже принадлежит, и предыдущий ресурс будет освобожден только тогда, когда новый ресурс станет владельцем.
интерфейс
Интерфейс, предоставляемый unique_ptr, аналогичен интерфейсу традиционного указателя, но не поддерживает арифметику указателя.
unique_ptr
обеспечитьrelease()
способ освобождения от права собственности.release
а такжеreset
Разница в том, чтоrelease
освобождать только право собственности, но не ресурсы,reset
Также освобождайте ресурсы.
Какой использовать?
Полностью зависит от того, как вы хотите владеть ресурсом, если вам нужно разделить использование ресурсаshared_ptr
, если ресурс используется исключительно, он используетсяunique_ptr
.
Помимо,shared_ptr
Сравниватьunique_ptr
Тяжелее, потому что ему также нужно выделить место для других вещей, таких как хранение сильных счетчиков ссылок и слабых счетчиков ссылок. а такжеunique_ptr
В этом нет необходимости, ему просто нужно хранить исключительно объект ресурса.