unique_ptr smart shared_ptr make_unique c++ c++11 c++14 unique-ptr

c++ - smart - shared_ptr



C++ std:: unique_ptr: ¿Por qué no hay tarifas de talla con lambdas? (4)

De hecho , habrá una penalización de tamaño para lambdas que no son apátridas, es decir, lambdas que capturan uno o más valores.

Pero para las lambdas que no capturan, hay dos hechos clave que hay que notar:

  • El tipo de lambda es único y conocido solo por el compilador.
  • Las lambdas que no capturan son apátridas.

Por lo tanto, el compilador puede invocar el lambda exclusivamente en función de su tipo , que se registra como parte del tipo de unique_ptr ; no se requiere información adicional de tiempo de ejecución .

De hecho, este es el motivo por el cual las lambdas que no capturan son apátridas. En términos de la cuestión de la pena de tamaño, por supuesto no hay nada especial acerca de las lambdas que no capturan en comparación con cualquier otro tipo de functor de eliminación sin estado.

Tenga en cuenta que std::function no es sin estado, por lo que el mismo razonamiento no se aplica a él.

Finalmente, tenga en cuenta que, aunque normalmente se requiere que los objetos sin estado tengan un tamaño distinto de cero para garantizar que tengan direcciones únicas, no es necesario que las clases base sin estado se agreguen al tamaño total del tipo derivado; esto se llama optimización de la base vacía. Por unique_ptr tanto, unique_ptr se puede implementar (como en la respuesta de Bo Perrson) como un tipo que se deriva del tipo de eliminador, que, si es sin estado, no contribuirá con una penalización de tamaño. (De hecho, esta puede ser la única forma de implementar correctamente unique_ptr sin una penalización de tamaño para los modificadores sin estado, pero no estoy seguro).

Estoy leyendo "C ++ moderno efectivo". En el ítem relacionado con std::unique_ptr se establece que si el eliminador personalizado es un objeto sin estado, entonces no se cobran tarifas de tamaño, pero si se trata de un puntero a la función o de una tarifa de tamaño de std::function ocurre. ¿Podrías explicar por qué?

Digamos que tenemos el siguiente código:

auto deleter_ = [](int *p) { doSth(p); delete p; }; std::unique_ptr<int, decltype(deleter_)> up(new int, deleter_);

A mi entender, unique_ptr debería tener un objeto de tipo decltype(deleter_) y asignarle deleter_ a ese objeto interno. Pero obviamente eso no es lo que está sucediendo. ¿Podría explicar el mecanismo detrás de esto usando el ejemplo de código más pequeño posible?


Desde una implementación unique_ptr :

template<class _ElementT, class _DeleterT = std::default_delete<_ElementT>> class unique_ptr { public: // public interface... private: // using empty base class optimization to save space // making unique_ptr with default_delete the same size as pointer class _UniquePtrImpl : private deleter_type { public: constexpr _UniquePtrImpl() noexcept = default; // some other constructors... deleter_type& _Deleter() noexcept { return *this; } const deleter_type& _Deleter() const noexcept { return *this; } pointer& _Ptr() noexcept { return _MyPtr; } const pointer _Ptr() const noexcept { return _MyPtr; } private: pointer _MyPtr; }; _UniquePtrImpl _MyImpl; };

La clase _UniquePtrImpl contiene el puntero y deriva del deleter_type .

Si el eliminador es apátrida, la clase base puede optimizarse para que no tome bytes por sí misma. Entonces todo el unique_ptr puede tener el mismo tamaño que el puntero contenido, es decir, el mismo tamaño que un puntero ordinario.


Si el eliminador es sin estado, no se requiere espacio para almacenarlo. Si el eliminador no es apátrida, entonces el estado debe almacenarse en el unique_ptr .
std::function punteros std::function y function tienen información que solo está disponible en el tiempo de ejecución y debe almacenarse en el objeto junto al puntero del objeto. Esto a su vez requiere asignar espacio (en el unique_ptr sí mismo) para almacenar ese estado adicional.

Tal vez comprender la Optimización de base vacía lo ayude a comprender cómo esto podría implementarse en la práctica.
El rasgo de tipo std::is_empty es otra posibilidad de cómo esto podría implementarse.

La forma exacta en que los escritores de bibliotecas implementan esto depende de ellos y de lo que permite el estándar.


Un unique_ptr siempre debe almacenar su eliminador. Ahora, si el eliminador es un tipo de clase sin estado, entonces unique_ptr puede hacer uso de la optimización de base vacía para que el eliminador no use ningún espacio adicional.

Cómo se hace esto exactamente difiere entre implementaciones. Por ejemplo, tanto libc++ como MSVC almacenan el puntero administrado y el eliminador en un par comprimido , que automáticamente obtiene la optimización de la base vacía si uno de los tipos implicados es una clase vacía.

Del enlace libc ++ arriba

template <class _Tp, class _Dp = default_delete<_Tp> > class _LIBCPP_TYPE_VIS_ONLY unique_ptr { public: typedef _Tp element_type; typedef _Dp deleter_type; typedef typename __pointer_type<_Tp, deleter_type>::type pointer; private: __compressed_pair<pointer, deleter_type> __ptr_;

libstdc ++ almacena los dos en un std::tuple y algunas búsquedas en Google sugieren que su implementación de tuple emplea optimización de base vacía, pero no puedo encontrar ninguna documentación que lo indique de manera explícita.

En cualquier caso, este ejemplo demuestra que tanto libc ++ como libstdc ++ usan EBO para reducir el tamaño de un unique_ptr con un eliminador vacío.