Умные указатели семантики владения и равенства
У меня есть пара вопросов для умных указателей, которые раньше я им не давал.
- Что значит владеть объектом, указывать на объект и управлять объектом в мире умных указателей? Ранее я думал, что тот, кто владеет объектом, также указывает на него и управляет объектом. Теперь я знаю, что умный указатель может владеть объектом, но указывать на другой объект (псевдонимы-конструкторы). Здесь я нашел действительно хорошее объяснение того, что означает владение объектом -> http://www.umich.edu/~eecs381/handouts/C++11_smart_ptrs.pdf, но все же я не могу сделать различие между этими 3 терминами.
- Если указатель владеет объектом, но указывает на другой объект, каким объектом он управляет? Тот, кому он принадлежит, или тот, на кого он указывает, или оба? Какой смысл владеть объектом, а не указывать на него?
- Когда два умных указателя равны? Могут ли два указателя иметь один и тот же объект и быть разными в одно и то же время? Меня интересует не равенство их ценностей, а вопрос о собственности.
- Почему важен порядок владения (помимо использования указателей в качестве ключей в контейнерах)? Я думаю, что это актуально только для shared_ptr.
Все началось с попытки понять owner_before, теперь я более запутался, чем раньше, я начал изучать эту тему..:(
3 ответа
Я думаю, что все ваше замешательство исходит от "конструктора псевдонимов":
template <typename U>
shared_ptr(const shared_ptr<U>& x, element_type* p)
Какая польза от этой вещи? Ну, он редко используется, но он "разделяет" право собственности на некоторый объект x
но когда вы разыграете его, вы получите p
вместо. Это все. Никогда не удалит p
или сделать что-нибудь еще с этим.
Это может быть полезно, если у вас есть что-то вроде этого:
struct Foo {
Bar bar;
};
struct Baz {
Baz(shared_ptr<Bar> bar) : m_bar(bar) {}
shared_ptr<Bar> m_bar;
};
int main()
{
auto foo = make_shared<Foo>();
Baz baz(shared_ptr<Bar>(foo, &foo.bar));
}
Сейчас baz
получает управлять временем жизни foo
не зная, что это делает - он заботится только о том, что он управляет временем жизни Bar
, но так как наши bar
это часть foo
мы не можем уничтожить foo
без разрушения bar
, поэтому мы используем конструктор псевдонимов.
Но на самом деле мы этого не делаем, потому что этот вариант использования очень редкий.
Владение
Ваша путаница в том, что владение не является чем-то, что компилятор может проверить; Вы, как программист, должны сделать это.
Мы можем сказать любой объект p
владеет q
если существование p
гарантирует существование q
(желательно без утечек памяти).
Простой случай - прямое владение, где освобождение p
также освободит q
например, если q
является членом p
, или же q
явно освобожден с delete
в p
деструктор.
Умные указатели делают это очевидным для людей. Если q
хранится в std::unique_ptr
член p
, мы знаем это p
владеет q
, Вам не нужно искать (возможно, отсутствует или дублируется) оператор удаления.
Собственность также является переходной, если p
владеет q
а также q
владеет r
, затем p
должен владеть r
,
Aliasing
Если p
напрямую владеет q
и мы хотим создать shared_ptr
который владеет q
то он тоже должен владеть p
, В противном случае, если p
уничтожен, то q
будет тоже, несмотря на существование нашего общего указателя.
Это то, что делает конструктор псевдонимов для std::shared_ptr (продемонстрировано в ответе Джона).
Расширяется q
продлить p
время жизни, поэтому у нас есть указатель на q
который на самом деле владеет p
, Мы утверждаем компилятору, что p
на самом деле владеет q
так что общий птр транзитивно владеет q
,
Если p
не владеет q
, тогда ваша программа скомпилируется, но она не работает, как если бы вы вручную вызывали delete
дважды.
Сравнения
Для интеллектуальных указателей stl сравнения передаются в необработанный указатель. Таким образом, умные указатели равны, если они обращаются к одному и тому же объекту, а сравнения касаются места в памяти. Не существует каких-либо определенных поведений, определяющих области памяти, поэтому вы не можете использовать его для чего-то другого, кроме хранения в map
или же set
,
Старый "сырой" указатель:
someType* pR = new someType(param1, param2); //pR is a pointer
MyOwner.TakeOwnershipOf(pR); // Now MyOwner is the owner, **the one who ONLY should call 'delete pR;'**
Умный указатель:
std::shared_ptr<someType> sp = std::make_shared<someType>(param1, param2);
"sp" теперь является владельцем (в коде нет "pR", но внутренне это так). И вам не нужно называть "удалить pR". Это объект, который внутренне хранит указатель на pR и удаляет его, когда он больше не нужен.
sp это объект. "sp->any" точно так же, как "pR->any". Это может сбить вас с толку о том, что sp также является указателем. Нет, бывает, что "->" перегружен.
Больше перегружено:
sp.get()
такой же как pR
, *sp
такой же как *pR
sp1 == sp2
такой же как pR1 == pR2
и так же, как sp1.get()==sp2.get()
Хорошая вещь о shared_ptr, например, когда вам нужно хранить один и тот же указатель в нескольких контейнерах. Если какой-либо контейнер удален, сохраненный указатель (sp, которому принадлежит pR) также должен быть удален. Это сделает недействительным указатель, хранящийся в другом контейнере. Вместо проверки других контейнеров на предмет наличия этого указателя, shared_ptr берет на себя ответственность за него. Это вы можете безопасно удалить sp
так как pR
будет удален только последним контейнером, который хранит sp
,