Программисту С++ по наследству приходится работать с памятью. Это вам не всякие детские языки типа Java, C#, Python, PHP и другие! Тут думать надо!..
Шутки шутками, но распространенная ошибка у моих студентов - утечка памяти. Пример (гипотетический) плохого кода:
copy to clipboardподсветка кода- struct AStruct{};
- void aFunction(AStruct*){}
-
- int main(){
- aFunction(new AStruct); // what happend with allocated memory?!
- return 0;
- }
Естественно, постоянно помнить, что надо выделять память, а потом её освобождать - сложно. Поэтому были придуманы умные указатели - специальные классы, которые выделяют автоматически память под нужный тип данных, а когда уже она нам не нужна - освобождать эту память. Эдакие "умные" указатели. (Которые, кстати, в стандартной библиотеке С++ давно существуют.)
Идея "умного" указателя проста: некоторый класс выделяет в конструкторе необходимую память, а в деструкторе он её освобождает. При помощи шаблонов это запишется следующим образом:
copy to clipboardподсветка кода- template
- class SmartPtr{
- TYPE* ptr;
- public:
- SmartPtr(): ptr(new TYPE()) {}
- SmartPtr(TYPE value): ptr(new TYPE()) { *ptr = value; }
- ~SmartPtr() { delete ptr; }
-
- TYPE& operator *() { return *ptr; }
- const TYPE& operator *() const { return *ptr; }
-
- SmartPtr(const SmartPtr&);
- SmartPtr operator = (const SmartPtr&);
- };
Пока всё красиво получается. Однако мы сталкиваемся с проблемой, когда необходимо реализовать копирование одного умного указателя в другой:
copy to clipboardподсветка кода- SmartPtr ptr(10);
- SmartPtr ptr2 = ptr; // what happens?!
В этом случае можно предложить 4 решения:
1. запретить копирование объектов. Можно объявить конструктор копирования и оператор копирования приватными, тогда память будет всегда только у одного объекта.
2. Память из копируемого объекта переходит новому объекту, во владение.
copy to clipboardподсветка кода- template
- SmartPtr::SmartPtr(const SmartPtr& p){
- ptr = p.ptr;
- p.ptr = nullptr;
- }
-
- template
- SmartPtr SmartPtr::operator = (SmartPtr& p){
- ptr = p.ptr;
- p.ptr = nullptr;
- return *this;
- }
Так, как я помню, работает std::unique_ptr.
3. в конструкторе копирования выделять новую память, в операторе копирования переписывать только значение.
copy to clipboardподсветка кода- template
- SmartPtr::SmartPtr(const SmartPtr& p){
- ptr = new TYPE();
- *ptr = *(p.ptr);
- }
-
- template
- SmartPtr SmartPtr::operator = (const SmartPtr& p){
- ptr = new TYPE();
- *ptr = *(p.ptr);
- return *this;
- }
Похожим образом работает std::auto_ptr (на самом деле он при копировании каждый раз перераспределяет память заново).
4. последний способ - считать количество объектов, ссылающихся на память. У всех объектов, созданных копированием,память является общей. И последний удаляемый объект должен эту память освобождать. Это достигается при помощи статических функций и переменных. Реализация данного указателя немного посложнее предыдущих, а мне уже лень, поэтому ограничусь замечанием, что так работает std::shared_ptr.
Каждый из типов "умного" указателя имеет свои особенности, диктующие их использование. Например, возникает вопрос, когда считать указатели равными. Некоторые типы указателей нельзя использовать с контейнерами (std::unique_ptr).
Вообще, использование умных указателей очень интересно и полезно. Советую ознакомиться с
Андрей Александреску "Современное проектирование на С++",
Герб Саттер "Решение сложных задач на С++"