Copy и move семантика



Понятие copy семантики и move семантики тесно связано с темой интеллектуальных указателей и само является темой для отдельного обсуждения. Здесь же мы остановимся на базовых определениях. copy семантика предполагает вызов конструктора копирования или оператора присваивания. Рассмотрим, реализацию функции swap.

1 void swap(int& a, int& b)

2 {

3 inttmp = a;

4 a = b;

5 b = tmp;

6 }

В данной реализации три операции копирования. Это приемлемо для int, но предположим, что мы должны произвести операцию обмена для string. Допустим, реализация string имеет поле char* data, которое указывает на строку и поле size_tsize, хранящее размер строки. Тогда реализация swap для строк, использующая move семантику, выглядит следующим образом.

1 void swap(string& a, string& b)

2 {

3 const char* p = a.data();

4 size_tsz = a.size();

5 a.data() = b.data();

6 a.size = b.size();

7 b.data = p;

8 b.size = sz;

9 }

Как нетрудно заметить, при помощи move семантики у нас получилось избежать дорогостоящего копирования всех элементов строк. Рекомендуется реализовывать метод swap для пользовательских типов. Это может значительно улучшить эффективность кода, например, при использовании контейнеров STL, которые будут предпочитать пользовательскую реализацию, а не дефоултовую, которая использует copy семантику.1 class A

2 {

3 public:

4 void swap(A&rhs);

5 };

unique_ptr

Прежде чем начать говорить про uniq_ptr следует упомянуть его предшественника – класс auto_ptr. auto_ptr является интеллектуальным указателем, который реализует строгую стратегию владения объектом. Т.е. на один и тот же объект не может ссылаться более одного экземпляра auto_ptr. Основным недостатком auto_ptr является передача прав владения объектом через синтаксис операции копирования. В следующем примере объявляется и инициализируется переменная a, значение которой затем присваивается переменной b.

1 auto_ptr<int>a(new int(10));

2 auto_ptr<int> b = a;

3 cout<< "a: " << *a <<std::endl; // access violation error!

В момент присваивания владение объектом int(10) передается от a к b. После этой операции a указывает на null_ptr и попытка разыменовать a окончится ошибкой доступа к памяти. Проблема в том, что конструктор копирования и оператор присваивания в большинстве случаев предполагают использование copy, а не move семантики. Реализация auto_ptr является причиной того что его нельзя использовать в контейнерах и алгоритмах STL. Некоторые алгоритмы (например, реализации sort) предполагают, что конструктор копирования реализует copy семантику.

unique_ptr решает проблему передачи владения объектом явным образом путем использования функции move.

1 unique_ptr<int>a(new int(10));

2 unique_ptr<int> b = std::move(a);

При этом конструктор копирования и оператор присваивания объявлены как private, поэтому следующий код не скомпилируется.

1 unique_ptr<int>a(new int(10));

2 unique_ptr<int> b = a;

В некоторых случаях, впрочем, допускаются исключения этому правилу. Например, в следующем примере компилятор знает, что временный экземпляр unique_ptr будет удален при выходе из функции CreatePtr и разрешает вызов конструктора копирования.

1 unique_ptr<int>CreatePtr()

2 {

3 return unique_ptr<int>(new int(10));

4 }

5 unique_ptr<int>a(CreatePtr());

Такой прием называется идиомой SinkandSource. Это сделано из соображений облегчения использования unique_ptr. Предположим, мы собираемся хранить вектор из unique_ptr. Операция push_back для вставки в конец вектора требует наличия открытого конструктора копирования. По идее следующий код не должен компилироваться.

1 vector<unique_ptr<int>> v;

2 v.push_back(unique_ptr<int>(new int(0)));

Но в данном случае нас выручает идиома SinkandSource, которая разрешает неявно вызывать конструктор копирования unique_ptr, зная, что временный объект будет гарантированно удален. А вот такой код не скомпилируется, поскольку переменная a не является временной переменной и идиома SinkandSource не сработает.

1 vector<unique_ptr<int>> v;

2 unique_ptr<int>a(new int(0));

3 v.push_back(a);

unique_ptrпозволяет указать каким образом будет удаляться память, выделенная под объект. Для этого нужно вторым параметром шаблона указать функтор, который будет удалять память в деструкторе unique_ptr.

1 structmalloc_deleter

2 {

3 void operator ()(void* ptr)

4 {

5 free(ptr);

6 }

7 };

9 unique_ptr<int, malloc_deleter>a((int*) malloc(sizeof(int)), malloc_deleter());

Еще одной интересной особенностью unique_ptr является возможность работы с массивами.

1 intsize = 3;

2 unique_ptr<int[]> a(new int[size]);

3 a[0] = 1;

4 a[1] = 2;

В большинстве случаев для работы с массивами лучше пользоваться классом vector, но существует достаточное количество сторонних библиотек, функции которых получают указатель на массив и размер массива. В таких случаях приходится выделять буфер нужного размера, который нужно как-то удалить после использования. unique_ptr гарантирует автоматическое удаление массива при выходе из контекста или возникновении исключения.

1 constintsize = 1024;

2 unique_ptr<int[]>buf(new int[size]);

4 fread(buf.get(), sizeof(int), size, file);

shared_ptr

shared_ptr является интеллектуальным указателем с подсчетом ссылок. Он допускает наличие нескольких экземпляров shared_ptr, которые указывают на один и тот же объект. Наряду с данными shared_ptr хранит счетчик ссылок – целое число, которое указывает, сколько экземпляров shared_ptr ссылаются на объект. Счетчик увеличивается в конструкторе копирования и операторе присваивания и уменьшается в деструкторе. Если в деструкторе shared_ptr выясняется, что счетчик достиг нуля, то это означает что на объект никто больше не ссылается и его можно удалить.

Существует два способа создания экземпляров shared_ptr.

1 shared_ptr<int>sp1(new int(10));

2 shared_ptr<int>sp2(make_shared<int>(10));

Первый способ более очевидный, вконструктор передается указатель на экземпляр int, на который будет ссылаться shared_ptr. Во втором случае вызывается функция make_shared, которая сама создает экземпляр int. Но именно второй способ является предпочтительным. Основная причина – это эффективность. Дело в том, что любая реализация shared_ptr хранит указатель на экземпляр с данными и указатель на т.н. контрольный блок – служебную информацию, к которой относится число ссылок и число слабых (weak) ссылок. В случае если библиотека сама создает объект, то есть возможность разместить контрольный блок и данные в одном месте. Соответственно, во втором случае мы получаем выигрыш в производительности, когда сокращаем количество операций выделения памяти и удаления с двух до одной.

weak_ptr

В некоторых случаях механизм подсчета ссылок, использующийся в shared_ptr, является источником проблем. Предположим, у нас имеется класс A и класс B. Класс A имеет поле _b, которое является указателем на экземпляр B. Класс B имеет поле _a, которое является указателем на экземпляр A.

01 struct A

02 {

03 shared_ptr<B> _b;

05 ~A()

06 {

07 cout<< "Destroying A" <<std::endl;

08 }

09 };

11 struct B

12 {

13 shared_ptr<A> _a;

15 ~B()

16 {

17 cout<< "Destroying B" <<std::endl;

18 }

19 };

В следующем примере объекты, на которые ссылаются a и b никогда не удалятся из-за циклической зависимости.

1 voidTest()

2 {

3 shared_ptr<A> a = make_shared<A>();

4 shared_ptr<B> b = make_shared<B>();

6 a->_b = b;

7 b->_a = a;

8 }

Для решения подобным проблем служит класс weak_ptr. Он имеет такую же семантику, как и shared_ptr, только не изменяет количество ссылок. Он предназначен для использования вместе с shared_ptr, когда увеличивать количество ссылок нецелесообразно по каким-то причинам. Типичный пример его использования выглядит так.

1 shared_ptr<int> sp1 = make_shared<int>(10);

2 weak_ptr<int>wp = sp1;

4 if(shared_ptr<int> sp2 = wp.lock())

5 {

6 *sp2 = 11;

7 }

Метод lock возвращает экземпляр shared_ptr, указывающий на объект, если объект еще не удалился к тому моменту. Если объект удалился по причине обнуления ссылок в оригинальномshared_ptr, то возвращается экземпляр shared_ptr, который указывает на nullptr.


Дата добавления: 2015-12-21; просмотров: 23; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!