shared_ptr make_unique example c++ c++11 shared-ptr unique-ptr nullptr

c++ - example - make_unique



¿El comportamiento estándar para los borradores difiere entre shared_ptr y unique_ptr en el caso de los punteros nulos? (2)

OK, primero algunas cosas que podrían ser relevantes:

Estoy usando el compilador Clang 3.1, en modo C ++ 11, con la biblioteca estándar establecida en libc ++.

Estoy tratando de familiarizarme con C ++ 11, y al hacerlo me encontré con un comportamiento que me parece extraño. Puede ser una peculiaridad de Clang o libc ++ pero no puedo hablar C ++ standardese y no tengo acceso a otros compiladores con soporte para C ++ 11, así que realmente no puedo verificarlo, y busqué en Internet y en Stack Overflow lo mejor que puedo sin encontrar nada relacionado ... así que aquí vamos:

Cuando se usa shared_ptr / unique_ptr para implementar RAII para un recurso simple, parece que su comportamiento difiere con respecto a los punteros nulos al eliminarse. Me doy cuenta de que normalmente no es necesario eliminar un puntero nulo, pero esperaba que el comportamiento coincidiera al menos entre los dos punteros inteligentes STL.

Para el caso específico, considere el siguiente código:

{ auto Deleter = [](void *){cout << "It''s later!" << endl;}; shared_ptr<void> spDoSomethingLater(nullptr, Deleter); unique_ptr<void, void (*)(void *)> upDoSomethingLater(nullptr, Deleter); cout << "It''s now!" << endl; }

Hubiera esperado uno de los siguientes resultados de esto:

a) Si se llaman los dos borradores aunque el puntero sea nulo:

"It''s now!" "It''s later!" "It''s later!"

b) Si no se llama a ninguno de los borradores porque el puntero es nulo:

"It''s now!"

Pero no observo ninguno de estos casos. En cambio, observo:

"It''s now!" "It''s later!"

Lo que significa que uno pero no el otro de los borradores está siendo llamado. Tras una investigación adicional, descubrí que se llama al deleter para shared_ptr independientemente de si tiene un valor nulo, pero al deleter de unique_ptr solo se llama si no tiene un valor nulo.

Mis preguntas: ¿Es este realmente el comportamiento correcto según lo especificado por la norma? Si es así, ¿por qué el comportamiento especificado difiere entre los dos tipos de STL de esta manera? Si no es así, ¿es esto un error que debería informar a libc ++?


El comportamiento observado está de acuerdo con la norma.

Para unique_ptr , 20.7.1.2.2 / 2 (efectos del destructor) dice

Efectos: Si get() == nullptr no hay efectos. De get_deleter()(get()) contrario, get_deleter()(get()) .

Para shared_ptr , 20.7.2.2.2 / 1 dice que se debe llamar al eliminador incluso si envuelve el puntero nulo:

Efectos:

  • Si * está vacío o comparte la propiedad con otra instancia de use_count() > 1 ), no hay efectos secundarios.
  • De lo contrario, si * esto posee un objeto p y un deleter d , se llama a d(p) .
  • De lo contrario, * esto posee un puntero p , y se llama delete p .

El detalle importante aquí es la expresión "posee un objeto p ". 20.7.2.2/1 dice que "un objeto shared_ptr está vacío si no posee un puntero". 20.7.2.2.1 / 9 (el constructor relevante) dice que "construye un objeto shared_ptr que posee el objeto p y el marcador d ".

Por lo que puedo decir, esa invocación técnicamente hace que shared_ptr posea el puntero nulo, lo que hace que se llame al eliminador. Contraste esto con el constructor sin parámetros que se dice que deja el shared_ptr " vacío ".


Sí, es el comportamiento correcto.

§20.7.1.2.2 [unique.ptr.single.dtor] / 2:

unique_ptr destructor

Efectos : Si get() == nullptr no hay efectos. De get_deleter()(get()) contrario, get_deleter()(get()) .

§20.7.2.2.2 [util.smartptr.shared.dest] / 1:

shared_ptr destructor

Efectos :

  • Si *this está vacío o comparte la propiedad con otra instancia de use_count() > 1 ), no hay efectos secundarios.
  • De lo contrario, si *this posee un objeto p y un deleter d , se llama a d(p) .
  • De lo contrario, *this posee un puntero p , y se llama delete p .

¿Entonces no debería haber ningún efecto ya que shared_ptr está vacío? Incorrecto, porque proporcionar un puntero hace que shared_ptr no esté vacío, incluso si el puntero es nulo.

§20.7.2.2.1 [util.smartptr.shared.const] / 8–10

constructores shared_ptr

template<class Y, class D> shared_ptr(Y* p, D d); template<class Y, class D, class A> shared_ptr(Y* p, D d, A a); template <class D> shared_ptr(nullptr_t p, D d); template <class D, class A> shared_ptr(nullptr_t p, D d, A a);

Requiere: p será convertible a T* . ...

Efectos: construye un objeto shared_ptr que posee el objeto p y el deleter d .

Postcondiciones: use_count() == 1 && get() == p .

Esto significa que shared_ptr posee el nullptr. Por lo tanto, se llamará al eliminador cuando se comparta shared_ptr.