четверг, февраля 25, 2016

Несколько интересных свойств умных указателей в C++

Об этом полезно знать при использовании умных указателей в С++:

У shared_ptr есть конструктор, который позволяет создавать зависимости между shared_ptr'ами. (std::shared_ptr's secret constructor  by Anthony Williams)
Допустим, я хочу создать указатель на объект типа Y, py, который является членом экземпляра класса X, px. Мне нужно, чтобы px не удалялся, пока я не закончу работать с py.


void bar(){
    std::shared_ptr<X> px(std::make_shared<X>());
    std::shared_ptr<Y> py(px,&px->y);
    store_for_later(py);
} // our X object is kept alive



unique_ptr не такой уж и уникальный. (unique_ptr–How Unique is it? by Bartosz Milewski)

void f(Foo * pf) {
    globalFoo = pf; // creates a global alias
}

unique_ptr<Foo> pFoo(new Foo());
f(pFoo.get()); // leaks an alias


Передача shared_ptr по значению - дорогое удовольствие. (The Real Price of Shared Pointers in C++ by Nico Josuttis)
При передаче по значению происходит его копирование и к его внутреннему счетчику прибавляется единица, это нужно сделать атомарно, что влияет на производительность. Передавайте его по ссылке, где возможно.


Зачем нужен scoped_ptr, если есть shared_ptr. (shared_ptr vs scoped_ptr)
shared_ptr "тяжелее" чем scoped_ptr, потому что он гарантирует корректную работу  в многопоточных программах. Поэтому, если вы работаете с одним указателем, и вам просто нужно автоматически освободить память из-под него, лучше использовать scoped_ptr.


Чтобы корректно возвращать shared_ptr на this надо использовать enable_shared_from_this. (std::enable_shared_from_this на cppreference.com)
Пример с  cppreference.com демонстрирует что случится, если вы не используете enable_shared_from_this.


#include <memory>
#include <iostream>

struct Good: std::enable_shared_from_this<Good>
{
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};
 
struct Bad
{
    std::shared_ptr<Bad> getptr() {
        return std::shared_ptr<Bad>(this);
    }
    ~Bad() { std::cout << "Bad::~Bad() called\n"; }
};
 
int main()
{
    // Good: the two shared_ptr's share the same object
    std::shared_ptr<Good> gp1(new Good);
    std::shared_ptr<Good> gp2 = gp1->getptr();
    std::cout << "gp2.use_count() = " << gp2.use_count() << '\n';
 
    // Bad, each shared_ptr thinks it's the only owner of the object
    std::shared_ptr<Bad> bp1(new Bad);
    std::shared_ptr<Bad> bp2 = bp1->getptr();
    std::cout << "bp2.use_count() = " << bp2.use_count() << '\n';
} // UB: double-delete of Bad


Бонусная ссылка:
Smart Pointer Parameters by Herb Sutter

21 коммент.:

Vadim Panin комментирует...

Если я не ошибаюсь, при копировании shared_ptr увеличение счетчика делается атомарным инкрементом (interlocked increment в VC и я не помню уже какой интринсик в gcc). Возможно на каких-то платформах это в самом деле через мьютекс сделано, но я пока таких не видел.

Alena комментирует...

Vadim Panin
Если я не ошибаюсь, при копировании shared_ptr увеличение счетчика делается атомарным инкрементом

Ага, мне уже об этом писали, я поправила.

Den Raskovalov комментирует...
Этот комментарий был удален автором.
Den Raskovalov комментирует...

Зачем передавать const shared_ptr<T>, если можно передать T*?

Sergei Nikulov комментирует...

Почему нельзя? Можно.
Вопрос баланса по стоимости.

Alexander Batishchev комментирует...

Привет, Атомарные операции дороже неатомарных?

Alexander комментирует...

Зачем передавать const shared_ptr, если можно передать T*?

Почему бы и не T& (с проверкой на nullptr где необходимо). Избегает сырых указателей и потенциальных проблем с их сохранением или освобождением.
Для функций обработки данных, которые не влияют на владение объектами, не должно быть принципиально откуда берется объект: указатель, локальная переменная, контейнер, умный указатель, и т.п. Ссылки выглядят наиболее подходящим вариантом, за исключением, опять же, потенциальных nullptr. Передачу shared_ptr в аргументе стоит интерпретировать как явное разделение владения объектом. Аналогично для unique_ptr, его передача должна означать передачу владения.

Sergey Vostrikov комментирует...
Этот комментарий был удален автором.
Sergey Vostrikov комментирует...

А как на счет enable_shared_from_this<>, если Good не находится в shared_ptr<>?

Good g1;
std::shared_ptr<Good> gp2 = g1.getptr(); // всё ли тут хорошо?

Alena комментирует...

2Den Raskovalov

Зачем передавать const shared_ptr<T>, если можно передать T*?

Чтобы у указателя стало на одного владельца больше.(вижу, что Alexander уже на это ответил).

Также можно передать const shared_ptr<T>&, если ты не уверен нужен тебе еще один владелец или нет, по ходу функции можно передумать и присвоить его.

2Alexander Batishchev

Привет, Атомарные операции дороже неатомарных?

Да, потому что тебе надо синхронизовать кэши ядер, нельзя менять порядок операций и пр. Джоссатис об этом подробно говорит.

Alena комментирует...

2Sergey Vostrikov

А как на счет enable_shared_from_this<>, если Good не находится в shared_ptr<>?

Good g1;
std::shared_ptr<Good> gp2 = g1.getptr(); // всё ли тут хорошо?


Не хорошо. Наличие хотя бы одно shared_ptr - обязательное условие

std::enable_shared_from_this allows an object t that is currently managed by a std::shared_ptr named pt to safely generate additional std::shared_ptr instances

Анонимный комментирует...

Алена, что вы думаете по поводу языка Rust, есть смысл его изучать? Знаний по с++ нету.

Alena комментирует...

Анонимный

Алена, что вы думаете по поводу языка Rust, есть смысл его изучать? Знаний по с++ нету.

Интересный новый язык, набирает популярность. Но вакансий на сегодняшний день очень-очень мало.

Volos_86 комментирует...

Жалко в РАДИО-Т больше не приходите...

Alena комментирует...

Volos_86
Жалко в РАДИО-Т больше не приходите...

Зато я недавно была в cppcast.

Анонимный комментирует...

Алена, а вы уже не в геймдеве?

Alena комментирует...

Анонимный
Алена, а вы уже не в геймдеве?

Я уже давно не в геймдеве.

AlixBZ комментирует...

Я уже давно не в геймдеве.

Простите за возможную бестактность с моей стороны, но с чем связана смена предметной области - с личными предпочтениями или с положением дел в игрострое?

Alena комментирует...

AlixBZ
Простите за возможную бестактность с моей стороны, но с чем связана смена предметной области - с личными предпочтениями или с положением дел в игрострое?

Предложение работы от Майкрософта было сильно лучше всех остальных на тот момент. Причем распределенные системы не были новой для меня отраслью, я на распределенных системах специализировалась в Универе.

Мария комментирует...

Эх хороший язык с++, жаль что давно забросила

Анонимный комментирует...

> shared_ptr "тяжелее" чем scoped_ptr, потому что он гарантирует корректную работу в многопоточных программах

но есть ньюанс:
https://habrahabr.ru/post/311560/

надо батарейки к нему не забыть:
http://en.cppreference.com/w/cpp/memory/shared_ptr/atomic