вторник, 7 февраля 2017 г.

Интеллектуальные указатели (Smart Pointers) C++




Интеллектуальный указатель - это объект класса, который действует подобно указателю, но обладает дополнительными возможностями.



В предыдущем стандарте C++03 в наличии имелось только два вида указателей, стандартный и один умный std::auto_ptr.


Проблема std::auto_ptr, выявляется в коде ниже:


1
2
3
auto_ptr<string> p1(new string("Hello"));
auto_ptr<string> p2;
p2 = p1;

В строке 3 p2 получает права владения объектом string, указатель p1 лишается этих прав. Это вроде бы хорошо так как вызывается один раз деструктор для указателя, а не два раза чтобы удалить один и тот же объект. Но вдруг программа где то использует данные из p1. А p1 уже не указывает на данные.

Пример с контейнером:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>
#include <memory>

int main()
{
    using namespace std;
    auto_ptr<string> films[5] =
    {
        auto_ptr<string> (new string("Hello")),
        auto_ptr<string> (new string("Goodbuy")),
        auto_ptr<string> (new string("Good morning")),
        auto_ptr<string> (new string("Good evening")),
        auto_ptr<string> (new string("Good afternoon"))
    };
    auto_ptr<string> pFilm;
    pFilm = films[3];

    for(int i(0); i < 5; ++i)
    {
        cout << *films[i] << endl;
    }
}

Элемент массива становится не валидным, так как в 16 строке мы передали права на объект указателю pFilm. Программа скомпилируется но выдаст ошибку в процессе выполнения.

***

std::unique_ptr - немного отличается от std::auto_ptr, тем что запрещает копирование. Это безопаснее так как мы увидим ошибку на этапе компиляции, а не увидим сбой программы и будем искать ошибку.

Для изменения прав на объект используется std::move

1
2
3
unique_ptr<string> p1(new string("Hello"));
unique_ptr<string> p2;
p2 = std::move(p1);

Как для std::auto_ptr так и для shared_ptr есть методы для сброса прав указателя reset(), для возвращения классического указателя используется метод get().

Подробнее:

std::unique_ptr – умный указатель, который: получает единоличное владение объектом через его указатель, и разрушает объект через его указатель, когда unique_ptr выходит из области видимости.
unique_ptr не может быть скопирован или задан через операцию присвоения, два экземпляра unique_ptr не могут управлять одним и тем же объектом. Неконстантный unique_ptr может передать владение управляемым объектом другому указателю unique_ptr. const std::unique_ptr не может быть передан, ограничивая время жизни управляемого объекта областью, в которой указатель был создан. Когда unique_ptr уничтожается, он удаляет объект с помощью Deleter.
Существует две версии std::unique_ptr:

1) управляет временем жизни одного объекта, например, созданного с помощью оператора new
2) управляет временем жизни массива, с длиной, определенной во время выполнения, созданного с помощью new[]

Типичные случаи применения std::unique_ptr включают:
обеспечение безопасности исключений для классов и функций, которые управляют объектами с динамическим временем жизни, гарантируя удаление в случае нормального завершения и завершения по исключению передача владения динамически созданным объектом в функции
получение владения динамически созданным объектом из функций в качестве типа элемента в контейнерах, поддерживающих семантику перемещения, таких как std::vector, которые хранят указатели на динамически выделенные объекты (например, если желательно полиморфное поведение)

Ссылка: http://ru.cppreference.com/w/cpp/memory/unique_ptr

***

std::shared_ptr - самый часто используемый интеллектуальный указатель. Несколько указателей данного класса могут ссылаться на один объект. std::shared_ptr - ведёт подсчёт ссылок на используемый ресурс. Ресурс освободится когда количество ссылок указателя будет равно 0.
Для просмотра количества ссылающихся указателей используется функция: std::shared_ptr.use_count()


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>
#include <memory>
using namespace std;

int main(int argc, char *argv[])
{
    shared_ptr<int> ptr1(new int(66));
    shared_ptr<int> empty_ptr;
    empty_ptr = ptr1;

    cout << empty_ptr.use_count();  // Выведет 2;
    return 0;
}

Для создания объектов на лету следует использовать фабричную функцию   std::make_shared<T>. Создает объект типа T и оборачивает его в std::shared_ptr использованием args как список параметров для конструктора T.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>
#include <memory>

void foo(std::shared_ptr<int> i)
{
    (*i)++;
}

int main()
{
    auto sp = std::make_shared<int>(10);
    foo(sp);
    std::cout << *sp << std::endl;
}

***

std::weak_ptr – умный указатель, который содержит "слабую" ссылку на объект, управляемый указателем std::shared_ptr. Он должен быть преобразован в std::shared_ptr, чтобы получить доступ к управляемому объекту.
Данный указатель позволяет решить проблему перекрёстных ссылок:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <memory>
using namespace std;

struct Foo
{
    shared_ptr<Foo> otherFoo;
};

void f()
{
    shared_ptr<Foo> a(new Foo);
    shared_ptr<Foo> b(new Foo);

    a->otherFoo = b;
    b->otherFoo = a;
    cout << a.use_count() << " : " << b.use_count(); 2 : 2
}

int main()
{
    f();
    return 0;
}

Что произойдет при выходе объектов a и b из области определения? В деструкторе уменьшатся ссылки на объекты. У каждого объекта будет счетчик = 1 (ведь a все еще указывает на b, а b — на a). Объекты “держат” друг друга и у нашего приложения нет возможности получить к ним доступ — эти объекты “потеряны”.
Для решения этой проблемы существует weak_ptr. Одним из типичных случаев создания перекрестных ссылок является случай, когда один объект владеет коллекцией других объектов.
Заменяем указатель на std::weak_ptr и проблема решена.


1
2
3
4
struct Foo
{
    weak_ptr<Foo> otherFoo;
};




0 коммент.:

Отправить комментарий