2012-09-13 16 views
17

Sto creando una classe che si interpone con un codice API di Windows, ora uno dei puntatori che devo inizializzare viene fatto chiamando una funzione nativa che la inizializza.Inizializzazione di std :: unique_ptr passando l'indirizzo del puntatore

miei puntatori sono di tipo std::unique_ptr con un deleter personalizzato, che chiama la funzione WinAPI deleter fornito, però non riesco a passare l'unique_ptr con l'& operatore di indirizzo per l'init funzione. Perché?

ho creato un esempio che illustra il mio 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); 
} 

Le cortecce compilatore e 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**)' 

risposta

18

Da qualche parte sotto le coperte, unique_ptr<foo> ha un membro di dati di tipo foo*.

Tuttavia, non è legittimo che un utente della classe modifichi direttamente tale membro dati. In questo modo non è necessario conservare gli invarianti di classe di unique_ptr, in particolare non libererebbe il vecchio valore del puntatore (se presente). Nel tuo caso speciale non hai bisogno che ciò accada, perché il valore precedente è 0, ma in generale dovrebbe accadere.

Per questo motivo unique_ptr non fornisce l'accesso al membro dati, ma solo a una copia del suo valore (tramite get() e operator->). Non è possibile ottenere foo** dal tuo unique_ptr.

Si potrebbe invece scrivere:

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

Questo è sicura rispetto alle eccezioni per la stessa ragione che std::unique_ptr<foo, custom_deleter> foo_ptr(new foo()); è sicura rispetto alle eccezioni: unique_ptr garantisce che tutto ciò che si passa al suo costruttore alla fine vengono eliminati utilizzando il deleter.

Btw, non custom_deleter necessario un operator()(foo*)? O mi sono perso qualcosa?

+0

Quindi la soluzione è 'shared_ptr' o tornare a puntatori prime? –

+0

E che ne dici di ottenere? – ForEveR

+0

@ForEverR: che ne pensi? Restituisce il * valore * di quel membro dati, non un puntatore a quel membro dati. –

5

Steve ha già spiegato qual è il problema tecnico, tuttavia, il problema sottostante va molto più in profondità: il codice utilizza un idioma utile quando si affrontano i puntatori nudi. Perché questo codice esegue un'inizializzazione in due passaggi (prima creare l'oggetto, quindi inizializzarlo) in primo luogo? Dal momento che si desidera utilizzare puntatori intelligenti, io suggerirei di adattare con attenzione il codice:

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

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

} 

Naturalmente, la ridenominazione init_foo()-create_foo() e averlo restituire un std::unique_ptr<foo> direttamente sarebbe meglio. Inoltre, quando si utilizza l'inizializzazione in due passaggi, è consigliabile prendere in considerazione l'utilizzo di una classe per includere i dati.

+0

visto che il mio 'init_foo' è una chiamata API di Windows, non posso cambiarlo. Quindi mi suggerisce di scrivere un involucro attorno a quella funzione? Perché però? La sicurezza delle eccezioni non è un problema per @SteveJessop. –

+2

@Tony: il codice sbi è solo "più pulito", la singola riga in 'main' fa quello che dice. Il mio codice ha bisogno di una variabile temporanea per poter creare la cosa che effettivamente vuoi. Non è preferibile, il mio codice è proprio quello che devi fare per chiamare 'init_foo' com'era. –

+0

@SteveJessop: "... e restituire un' std :: unique_ptr 'direttamente sarebbe meglio." – sbi

0

È possibile utilizzare il seguente trucco:

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) 

e poi:

std::unique_ptr<foo, custom_deleter> foo_ptr; 
init_foo(&ptr_setter(foo_ptr)); 
Problemi correlati