Умные указатели в C++

329
24 октября 2017, 02:29
void setQuackBehavior(QuackBehavior & qb) {
    quackBehavior = std::move(std::shared_ptr<QuackBehavior> (&qb));
}

Есть метод setQuackBehavior(), который принимает ссылку на абстрактный класс QuackBehavior. Этот метод находится в классе, который имеет член std::shared_ptr<QuackBehavior> quackBehavior.

Может ли код, который приведён выше, иметь право на существование? По-моему тут всё хорошо, но мало ли я что-то не учитываю, и в примере этого кода имеется какая-либо грубая ошибка.

Answer 1

Такой подход имеет ряд недостатков.

  1. Создание объекта отдельно от создание указателя.
  2. Код выше должен гарантировать, что объект создается с помощью new.
  3. Код выше должен гарантировать, что объект нигде не удаляется.
  4. Код выше должен гарантировать, что в объекте, на который ссылается qb нет умных указателей на наш объект (который this в данной функции-члене).
  5. Код выше должен гарантировать, что больше нет умных указателей на данный объект.
Теперь по каждому пункту подробнее.

1) Когда Вы создаете shared_ptr, выделяется блок динамической памяти для хранения служебных данных, например, счетчик ссылок, собственный delete'ор и т.д. Таким образом, у Вас будет одно выделение памяти для создания объекта, и одно для создания этого блока служебных данных. И объект и данные также будут лежать в разных участках памяти, поэтому, такая структура, помимо лишнего выделения памяти может еще быть не дружелюбной к кешу. Для того, чтобы выделить память под объект и указателя рядом, одним выделением, можно использовать std::make_shared. Она за одно выделение памяти сделает и указатель и динамический блок с данными.

2) Для данного shared_ptr не указан пользовательский метод удаления, а значит будет использоваться delete, поэтому объект должен быть создан с помощью new.

3) Объект будет автоматически уничтожен при уничтожении последнего умного указателя ссылки на него. Согласитесь, при вызове setQuackBehavior совершенно не очевидно, что внутри на объект создается shared_ptr.

Derived derived = new Derived;
obj.setQuackBehavior(*derived);

Возможно, когда-нибудь дальше появится что-то вроде delete derived; или еще-что-нибудь подобное.

4) Это скорее не к конкретно этому коду, а вообще к любому коду с shared_ptr. Если два объекта содержат указатели на друг друга (или более сложная схема), то это приведет к циклическим ссылкам и, фактически, к утечкам.

5) Этот код требует гарантии, что на этот объект более нет умных указателей.

std::shared_ptr<Derived> derived(new Derived);//или make_shared
obj.setQuackBehavior(*derived);//Злостная ошибка

При таком (или подобном) коде на один объект derived теперь приходится два блока со служебными данными для разных shared_ptr, а значит у нас и два счетчика ссылок и т.д. Эти указатели теперь живут "раздельно" и не знают о существовании друг друга. Понятное дело, что это приведет к преждевременному удалению объекта, обращению к несуществующим данным, прострелам памяти, двойному удалению и всем-всем прелестям не верной работы с памятью.

Ну и в Вашем коде лишний std::move. Объект и так временный, поэтому будет перемещен, а не скопирован.

Почему бы не сделать так:

void setQuackBehavior(std::shared_ptr<QuackBehavior> const &qb) {
    quackBehavior = qb;
}

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

obj.setQuackBehavior(std::make_shared<Derived>());

По-моему, такой код более очевиден и безопасен.

Answer 2

Не очень ясен смысл делать move для shared_ptr. Т.к. при обычном копировании вполне нормально инкрементируется счётчик ссылок.

А ошибка может быть в двойном удалении объекта, на который ссылается qb. Первый раз как обычный объект (надо смотреть код, где создаётся объект исходно), второй - при вызове деструктора последнего объекта std::shared_ptr.

READ ALSO
Почему не срабатывает событие @click?

Почему не срабатывает событие @click?

Событие select_list_currentClick(e) должно переключать класс open для того, чтобы появлялся селект как справа на скрине Но оно почему-то срабатывает очень...

334
jQuery - На телефонах owl.carousel показывает все слайды

jQuery - На телефонах owl.carousel показывает все слайды

На телефонах owlcarousel показывает все слайды

210
Динамическая подгрузка мета тегов

Динамическая подгрузка мета тегов

Есть сайт с записямиПользователю предлагаеться поделиться записью в Фейсбук

212