C++ Гайд Умные указатели для неумных

feel irl
Участник
Статус
Оффлайн
Регистрация
21 Дек 2018
Сообщения
677
Реакции[?]
294
Поинты[?]
16K
Всего умных указателей придумали 4 ещё в 2011 году, но один из них убрали из 17 стандарта.
Зачем нужны умные указатели и какая мотивация была, для добавления их в язык?
Рассмотрим такой пример.(украл с другого сайта)
C++:
bool foo();
void bar();

void goo() {
    int* ptr = new int(42);
    if (foo()) {
        bar();
        return;
    }
    delete ptr;
}
Какие проблемы есть у данного кода?
В первую очередь, нам важно то, что если foo() == 1, то тогда delete ptr; просто не выполнится и здесь мы получаем очень большую проблему. Ошибка чисто человеческая и достаточно часто подобные совершают именно новые разработчики.

Умные указатели должны решать проблему с тем, чтобы освобождать память.

Самый примитивный и простой для понимания auto_ptr.
Что с ним не так? Рассмотрим такую реализацию этого умного указателя.
C++:
template<typename T>
class auto_ptr {
private:
    T* ptr;
public:
    auto_ptr(T* ptr) : ptr(ptr) {}
    auto_ptr(const auto_ptr& rhs) : ptr(rhs.ptr) {}
    auto_ptr(auto_ptr&& rhs) { swap(ptr,rhs.ptr); }
    auto_ptr operator=(const auto_ptr& rhs) { ptr = rhs.ptr; return *this;}
    auto_ptr& operator=(auto_ptr&& rhs) { swap(ptr,rhs.ptr); return *this;}
    ~auto_ptr() {
        delete ptr;
    }
    T* operator->() { return ptr; }
    T& operator*() { return *ptr; }
    T* get() { return ptr; }
};
Здесь сразу можно обнаружить проблему, которую увидим на следующем примере:
C++:
void foo(auto_ptr<int> x) {
    *x = 14;
}

int main() {
    auto_ptr<int> a{new int(42)};
    foo(a);
}
Что-же здесь не так? Когда мы передаём умный указатель в функцию, то мы его копируем и таким образом у нас получается, что два умных указателя указывают на одну ссылку, но как только функция завершается, то у умного указателя там вызывается деструктор, как и в мейне, при этом ресурс в умном указателе был удалён ещё после завершения работы foo. Здесь мы получаем UB(неопределённое поведение), что почти всегда очень плохо. Поэтому его убрали из 17 стандарта.
Как же решили такую проблему?
Есть ещё два умных указателя — unique_ptr и shared_ptr. Первый запрещает копировать себя как-либо, а второй имеет в себе счётчик ссылок(т.е он сохраняет число которое говорит о том, сколько его копий вообще существует), который как раз и знает, когда надо удалять ресурс, который лежит в нём.
Как их использовать?
C++:
bool foo() {
    shared_ptr<int> a (new int(14));
    shared_ptr<int> b(a);
    *a = 42;
    return a == b;
}

bool bar() {
    unique_ptr<int> a = make_unique<int>(42); // функция, для создания объекта
    unique_ptr<int> b(a); // тут получим ошибку, копирование запрещено
    unique_ptr<int> c (std::move(a)); // тут перемещение, но так лучше не делать никогда
}
Писал немного сонный и чисто на лайте, если где-то что-то не так, то обязательно напишите. Не стоит злоупотреблять умными указателями в своём коде, при достаточно большом их количестве это становится менее эффективно, нежели чем при использовании обычных указателей, несмотря на удобства.
p.s не рассказал про weak_ptr & intrusive_ptr, ибо не думаю, что кому-либо понадобится
 
Сверху Снизу