Czy dtor shared_ptr wymaga użycia „deleter”?
Jego szeroko znany że możesz użyć ashared_ptr
aby zapisać wskaźnik do niekompletnego typu, o ile wskaźnik może zostać usunięty (z dobrze zdefiniowanym zachowaniem) podczas budowyshared_ptr
. Na przykład technika PIMPL:
struct interface
{
interface(); // out-of-line definition required
~interface() = default; // public inline member, even if implicitly defined
void foo();
private:
struct impl; // incomplete type
std::shared_ptr<impl> pimpl; // pointer to incomplete type
};
[main.cpp]
int main()
{
interface i;
i.foo();
}
[interface.cpp]
struct interface::impl
{
void foo()
{
std::cout << "woof!\n";
}
};
interface::interface()
: pimpl( new impl ) // `delete impl` is well-formed at this point
{}
void interface::foo()
{
pimpl->foo();
}
To działa jak„usuń obiekt” „obiekt właściciela” (*) jest tworzony podczas budowyshared_ptr
wpimpl( new impl )
i przechowywane po wymazaniu typu wewnątrzshared_ptr
. Ten „obiekt właściciela” jest później używany do niszczenia wskazywanego obiektu. Dlatego zapewnienie bezpieczeństwa powinno być bezpiecznew linii destruktorinterface
.
Pytanie: Gdzie Standard gwarantuje, że jest bezpieczny?
(*) Nie jest to błąd, jeśli chodzi o standard, patrz poniżej, ale wywołuje niestandardowy deleter lub wywołuje wyrażenie delete. Ten obiekt jest zazwyczaj przechowywany jako część obiektu księgowego, stosując wymazywanie typu i wywołując niestandardowe wyrażenie deleter / delete w funkcji wirtualnej. W tym momencie wyrażenie usuwania powinno być również dobrze ukształtowane.
Odwołując się do najnowszej wersji w repozytorium github (94c8fc71, wersja N3797), [util.smartptr.shared.const]
template<class Y> explicit shared_ptr(Y* p);
3 Wymaga:p
jest wymienialny naT*
. Y
powinien być kompletny. Ekspresjadelete p
będzie dobrze uformowany, będzie miał dobrze określone zachowanie i nie będzie rzucał wyjątków.
4 Efekty: Konstruuje ashared_ptr
obiekt, który jest właścicielem wskaźnikap
.
5 Postconditions:use_count() == 1 && get() == p
.
6 rzutów:bad_alloc
lub wyjątek zdefiniowany przez implementację, gdy nie można uzyskać zasobu innego niż pamięć.
Uwaga: Dla tego ctorshared_ptr
nie musi posiadać deletera. Przezdeleter, Standard wydaje się znaczyćniestandardowy deleter, tak jak podajesz podczas budowy jako dodatkowy parametr (lubshared_ptr
nabywa / dzieli się jedna od drugiejshared_ptr
, np. poprzez cesję). Zobacz także (zobacz także [util.smartptr.shared.const] / 9). Implementacje (boost, libstdc ++, MSVC i chyba każda implementacja sensowna) zawsze przechowują „obiekt właściciela”.
Jakdeleter jestniestandardowy deleter, destruktorshared_ptr
jest zdefiniowany w kategoriachdelete
(delete-expression), jeśli nie ma niestandardowego deletera:
[util.smartptr.shared.dest]
~shared_ptr();
1 Efekty:
Jeśli*this
jestpusty lub dzieli się własnością z innymshared_ptr
instancja (use_count() > 1
), nie ma skutków ubocznych.W przeciwnym razie, jeśli*this
jest właścicielem obiektp
i deleterd
, d(p)
jest nazywany.Inaczej,*this
jest właścicielem wskaźnikp
, idelete p
jest nazywany.Przyjmęzamiar jest to, że implementacja jest wymagana do poprawnego usunięcia zapisanego wskaźnika, nawet jeśli w zakresieshared_ptr
dtor, wyrażenie usuwające jest źle sformatowane lub wywołuje UB. (Wyrażenie delete musi być dobrze ukształtowane i mieć dobrze zdefiniowane zachowanie w ctor.) Więc pytanie brzmi
Pytanie: Gdzie to jest wymagane?
(A może jestem po prostu zbyt wybredny i oczywiste jest, że implementacje muszą używać „obiektu właściciela”?)