2012-09-13 21 views
17

Estoy creando una clase que interopera con algún código API de Windows, ahora uno de los punteros que tengo que inicializar se hace llamando a una función nativa que lo inicializa.Inicializando un std :: unique_ptr pasando la dirección del puntero

Mis punteros son de tipo std::unique_ptr con un Deleter costumbre, que llama a la función API de Windows proporciona Deleter, sin embargo no puedo dejar pasar la unique_ptr con el & operador de dirección a la init-función. ¿Por qué?

He creado un ejemplo que muestra mi problema:

#include <memory> 

struct foo 
{ 
    int x; 
}; 

struct custom_deleter {}; 

void init_foo(foo** init) 
{ 
    *init = new foo(); 
} 

int main() 
{ 
    std::unique_ptr<foo, custom_deleter> foo_ptr; 

    init_foo(&foo_ptr); 
} 

Los ladridos del compilador y dice:

source.cpp: In function 'int main()': 
source.cpp:19:21: error: cannot convert 'std::unique_ptr<foo, custom_deleter>*' to 'foo**' for argument '1' to 'void init_foo(foo**)' 

Respuesta

18

En algún lugar debajo de las sábanas, unique_ptr<foo> tiene un elemento de datos de tipo foo*.

Sin embargo, no es legítimo que un usuario de la clase modifique directamente ese miembro de datos. Hacerlo no necesariamente conservaría los invariantes de clase de unique_ptr, en particular, no liberaría el antiguo valor de puntero (si lo hubiera). En su caso especial, no necesita que eso suceda, porque el valor anterior es 0, pero en general debería suceder.

Por esta razón, unique_ptr no proporciona acceso al miembro de datos, solo a una copia de su valor (a través de get() y operator->). No puede obtener un foo** de su unique_ptr.

Se podría en cambio escribir:

foo *tmp; 
init_foo(&tmp); 
std::unique_ptr<foo, custom_deleter> foo_ptr(tmp); 

Ésta es una excepción-seguro para la misma razón que std::unique_ptr<foo, custom_deleter> foo_ptr(new foo()); es una excepción de seguridad: unique_ptr garantías de que todo lo que pase a su constructor finalmente se eliminan mediante el Deleter.

Por cierto, ¿no custom_deleter necesita un operator()(foo*)? ¿O me he perdido algo?

+0

Así que la solución es 'shared_ptr' o volver a los punteros primas? –

+0

¿Y qué tal obtener? – ForEveR

+0

@ForEverR: ¿qué hay de eso? Devuelve el * valor * de ese miembro de datos, no un puntero a ese miembro de datos. –

5

Steve ya ha explicado cuál es el problema técnico, sin embargo, el problema subyacente es mucho más profundo: el código emplea una expresión idiomática útil cuando se trata de punteros desnudos. ¿Por qué este código hace una inicialización en dos pasos (primero crea el objeto, luego lo inicializas) en primer lugar? Puesto que desea utilizar punteros inteligentes, sugeriría a adaptar cuidadosamente el código:

foo* init_foo() 
{ 
    return new foo(); 
} 

int main() 
{ 
    std::unique_ptr<foo, custom_deleter> foo_ptr(init_foo()); 

} 

Por supuesto, el cambio de nombre a init_foo()create_foo() y tener que devolver un std::unique_ptr<foo> directamente sería mejor. Además, cuando utiliza la inicialización en dos pasos, a menudo es aconsejable considerar el uso de una clase para envolver los datos.

+0

al ver que mi 'init_foo' es una llamada a la API de Windows, no puedo cambiarlo. Entonces, ¿sugieres que escriba un resumen sobre esa función? ¿Por qué sin embargo? La seguridad de las excepciones no es un problema por @SteveJessop. –

+2

@Tony: el código de sbi es simplemente "más limpio", la única línea en "main" hace lo que dice. Mi código necesita una variable temporal para crear lo que realmente deseas. Eso no es preferible, mi código es justo lo que tienes que hacer para llamar a 'init_foo' como estaba. –

+0

@SteveJessop: "... y tener que devolver un' std :: unique_ptr 'directamente sería mejor." – sbi

0

Se puede utilizar el siguiente truco:

template<class T> 
class ptr_setter 
{ 
public: 
    ptr_setter(T& Ptr): m_Ptr{Ptr} {} 
    ~ptr_setter() { m_Ptr.reset(m_RawPtr); } 

    ptr_setter(const ptr_setter&) = delete; 
    ptr_setter& operator=(const ptr_setter&) = delete; 

    auto operator&() { return &m_RawPtr; } 

private: 
    T& m_Ptr; 
    typename T::pointer m_RawPtr{}; 
}; 


// Macro will not be needed with C++17 class template deduction. 
// If you dislike macros (as all normal people should) 
// it's possible to replace it with a helper function, 
// although this would make the code a little more complex. 

#define ptr_setter(ptr) ptr_setter<decltype(ptr)>(ptr) 

y luego:

std::unique_ptr<foo, custom_deleter> foo_ptr; 
init_foo(&ptr_setter(foo_ptr)); 
Cuestiones relacionadas