¿Cómo pasar deleter para hacer compartido?


Desde C++11, debido a varias razones, los desarrolladores tienden a usar clases de puntero inteligente para objetos dinámicos de por vida. Y con esas nuevas clases de puntero inteligente, los estándares, incluso sugieren no usar operadores como new en su lugar, sugieren usar make_shared o make_unique para evitar algún error propenso.

Si nos gusta usar una clase puntero inteligente, como shared_ptr, podemos construir una como,

shared_ptr<int> p(new int(12));

También nos gustaría pasar un deleter personalizado a smart pointer clases,

shared_ptr<int> p(new int(12), deleter);

Por otro lado, si nos gusta usar make_shared asignar, por ejemplo. int, en lugar de usar new y shared_ptr constructor, como en la primera expresión anterior, podemos usar

auto ip = make_shared<int>(12);

Pero ¿qué pasa si también nos gusta pasar un deleter personalizado a make_shared, hay una manera correcta de hacerlo? Parece que los compiladores, al menos gcc, da un error a,

auto ip = make_shared<int>(12, deleter);
Author: ks1322, 2015-12-12

4 answers

Como otros han dicho, make_shared no se puede usar con un deleter personalizado. Pero quiero explicar por qué.

Los deleters personalizados existen porque asignaste el puntero de alguna manera especial, y por lo tanto necesitas ser capaz de desalocarlo de una manera correspondientemente especial. Bueno, make_shared asigna el puntero con new. Los objetos asignados con new deben ser desasignados con delete. Lo que el deleter estándar hace obedientemente.

En resumen, si puede vivir con la asignación predeterminada comportamiento, también puede vivir con el comportamiento predeterminado de asignación. Y si no puede vivir con el comportamiento de asignación predeterminado, debe usar allocate_shared, que utiliza el asignador proporcionado para asignar y desasignar el almacenamiento.

También, make_shared está permitido (y casi seguramente lo hará) asignar la memoria para T y el bloque de control para el shared_ptr dentro de la misma asignación. Esto es algo que su deleter realmente no puede saber o tratar. Mientras allocate_shared es capaz de manejarlo, ya que el asignador que proporcione puede realizar tareas de asignación y desasignación.

 37
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-12 20:31:48

A partir de la documentación , make_shared acepta una lista de argumentos con los que se construirá una instancia de T.
Además, la documentación dice que:

Esta función se usa típicamente para reemplazar la construcción std::shared_ptr(new T(args...)) de un puntero compartido desde el puntero raw devuelto por una llamada a new.

Debido a eso, puede deducir que no puede establecer un deleter personalizado .
Para hacer eso, tienes que crear el shared_ptr por ti mismo mediante el constructor correcto .
Como ejemplo de un constructor de la lista propuesta, puede usar:

template< class Y, class Deleter > 
shared_ptr( Y* ptr, Deleter d );

Así, el código será algo así como:

auto ptr = std::shared_ptr(new MyClass{arg1, arg2}, myDeleter);

En lugar de:

auto ptr = std::make_shared<MyClass>(arg1, arg2);
 11
Author: skypjack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-14 11:32:21

No se puede. make_shared<T> reenvía los argumentos proporcionados al constructor de type T. Se utiliza para el caso simple cuando se desea el deleter predeterminado.

 4
Author: KyleKnoepfel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-12 18:32:57

No se especifica cómo make_shared obtiene la memoria para el objeto (podría usar operator new o malloc o algún tipo de asignador), por lo que no hay manera de que un deleter personalizado pueda saber cómo hacer lo correcto. make_shared crea el objeto, por lo que también tiene que confiar en él para destruir el objeto correctamente y hacer la limpieza adecuada, sea lo que sea.

También nos gustaría pasar un deleter personalizado a las clases de puntero inteligente,

shared_ptr<int> p(new int(12), deleter);

No creo que esto sea un ejemplo muy realista. Un deleter personalizado se usa típicamente cuando el recurso se obtuvo de alguna manera especial. Si acaba de crearlo con new de esta manera, ¿por qué necesita un deleter personalizado de todos modos?

Si solo quieres que se ejecute algún código en la destrucción, ¡ponlo en un destructor! De esta manera, también puede usarlo con make_shared, por ejemplo,

struct RunSomethingOnDestruction {
  RunSomethingOnDestruction(int n) : i(n) { }
  ~RunSomethingOnDestruction() { /* something */ }
  int i;
};

auto px = std::make_shared<RunSomethingOnDestruction>(12);
std:shared_ptr<int> p(px, px->i);

Esto le da un shared_ptr<int> que es creado por make_shared (por lo que obtiene las optimizaciones de memoria hechas por make_shared) que ejecutará algún código personalizado sobre la destrucción.

 3
Author: Jonathan Wakely,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-13 00:58:44