указатели, умные и не очень

Mar 21, 2016 21:38

Программисту С++ по наследству приходится работать с памятью. Это вам не всякие детские языки типа Java, C#, Python, PHP и другие! Тут думать надо!..
Шутки шутками, но распространенная ошибка у моих студентов - утечка памяти. Пример (гипотетический) плохого кода:

copy to clipboardподсветка кода
  1. struct AStruct{};  
  2. void aFunction(AStruct*){}  
  3.   
  4. int main(){  
  5.   aFunction(new AStruct); // what happend with allocated memory?!  
  6.   return 0;  
  7. }  

Естественно, постоянно помнить, что надо выделять память, а потом её освобождать - сложно. Поэтому были придуманы умные указатели - специальные классы, которые выделяют автоматически память под нужный тип данных, а когда уже она нам не нужна - освобождать эту память. Эдакие "умные" указатели. (Которые, кстати, в стандартной библиотеке С++ давно существуют.)
Идея "умного" указателя проста: некоторый класс выделяет в конструкторе необходимую память, а в деструкторе он её освобождает. При помощи шаблонов это запишется следующим образом:

copy to clipboardподсветка кода
  1. template  
  2. class SmartPtr{  
  3.   TYPE* ptr;  
  4. public:  
  5.   SmartPtr(): ptr(new TYPE()) {}  
  6.   SmartPtr(TYPE value): ptr(new TYPE()) { *ptr = value; }  
  7.   ~SmartPtr() { delete ptr; }  
  8.   
  9.   TYPE& operator *() { return *ptr; }  
  10.   const TYPE& operator *() const { return *ptr; }  
  11.   
  12.   SmartPtr(const SmartPtr&);  
  13.   SmartPtr operator = (const SmartPtr&);  
  14. };  

Пока всё красиво получается. Однако мы сталкиваемся с проблемой, когда необходимо реализовать копирование одного умного указателя в другой:

copy to clipboardподсветка кода
  1. SmartPtr ptr(10);  
  2. SmartPtr ptr2 = ptr; // what happens?!  

В этом случае можно предложить 4 решения:
1. запретить копирование объектов. Можно объявить конструктор копирования и оператор копирования приватными, тогда память будет всегда только у одного объекта.
2. Память из копируемого объекта переходит новому объекту, во владение.

copy to clipboardподсветка кода
  1. template  
  2. SmartPtr::SmartPtr(const SmartPtr& p){  
  3.   ptr = p.ptr;  
  4.   p.ptr = nullptr;  
  5. }  
  6.   
  7. template  
  8. SmartPtr SmartPtr::operator = (SmartPtr& p){  
  9.   ptr = p.ptr;  
  10.   p.ptr = nullptr;  
  11.   return *this;  
  12. }  

Так, как я помню, работает std::unique_ptr.
3. в конструкторе копирования выделять новую память, в операторе копирования переписывать только значение.

copy to clipboardподсветка кода
  1. template  
  2. SmartPtr::SmartPtr(const SmartPtr& p){  
  3.   ptr = new TYPE();  
  4.   *ptr = *(p.ptr);  
  5. }  
  6.   
  7. template  
  8. SmartPtr SmartPtr::operator = (const SmartPtr& p){  
  9.   ptr = new TYPE();  
  10.   *ptr = *(p.ptr);  
  11.   return *this;  
  12. }  

Похожим образом работает std::auto_ptr (на самом деле он при копировании каждый раз перераспределяет память заново).
4. последний способ - считать количество объектов, ссылающихся на память. У всех объектов, созданных копированием,память является общей. И последний удаляемый объект должен эту память освобождать. Это достигается при помощи статических функций и переменных. Реализация данного указателя немного посложнее предыдущих, а мне уже лень, поэтому ограничусь замечанием, что так работает std::shared_ptr.

Каждый из типов "умного" указателя имеет свои особенности, диктующие их использование. Например, возникает вопрос, когда считать указатели равными. Некоторые типы указателей нельзя использовать с контейнерами (std::unique_ptr).
Вообще, использование умных указателей очень интересно и полезно. Советую ознакомиться с Андрей Александреску "Современное проектирование на С++", Герб Саттер "Решение сложных задач на С++"

С++, мысли, умные указатели, статьи писать, много букв

Previous post Next post
Up