2015-04-27 7 views
13

Ho una certa strategia di progettazione in cui il costruttore della mia classe è privato e può essere costruito solo dagli amici della classe. All'interno della funzione friend, sto cercando di creare un unique_pointer della mia classe usando std::make_unique ma non viene compilato. Il mio compilatore VC12 si lamentaLa funzione amico non è in grado di costruire un puntatore univoco della classe

c: \ programmi di studio (x86) \ Microsoft Visual 12.0 \ VC \ include \ memoria (1639): l'errore C2248: 'Spam :: Spam': non può accedere membro privato dichiarato in classe 'Spam'

Il codice in questione che non riesce durante la compilazione è il seguente

#include <memory> 
class Spam { 
public: 
    friend void Foo(); 

private: 
    Spam(int mem) :mem(mem) {} 
    int mem; 
}; 
void Foo() { 
    std::unique_ptr<Spam> spam = std::make_unique<Spam>(10); 
} 

Perché non sono in grado di compilare?

risposta

15

Nel tuo caso la funzione make_unique sta tentando di creare un'istanza di Spam e quella funzione non è un amico. Chiamare una funzione non amico dall'interno di una funzione di amicizia non infonde la funzione di non amico con lo stato di amico.

per risolvere questo si può scrivere in Foo:

std::unique_ptr<Spam> spam(new Spam(10)); 
+0

Sì, hai ragione. Potete aggiungere ulteriori dettagli alla risposta? – Abhijit

+0

Non c'è alcuna garanzia che 'make_unique' non possa delegare ad un helper interno, nel qual caso il tuo' amico' non funzionerà (per non dire che la firma è sbagliata anche perché 'make_unique' prende i suoi argomenti inoltrando il riferimento). –

+0

Hai ragione. L'ho cancellato. –

1

Nel tuo esempio, Foo() è un friend, ma non è la funzione che sta creando il Spam-make_unique sta chiamando internamente new Spam stessa. La semplice correzione è quello di avere solo Foo() effettivamente costruire il Spam direttamente:

void Foo() { 
    std::unique_ptr<Spam> spam(new Spam(10)); 
} 
13

Ecco un altro approccio che ho visto usato: hanno il costruttore pubblico richiedono un token di accesso privato. Però non ricordo il nome di questo modello.

class Spam { 
    static struct Token {} const token; 
    friend void Foo(); 
public: 
    Spam(Token const&, int mem) :mem(mem) {} 

private: 
    int mem; 
}; 

void Foo() { 
    std::unique_ptr<Spam> spam = std::make_unique<Spam>(Spam::token, 10); 
} 

void Bar() { 
    // error: 'Spam::Token Spam::token' is private 
    // std::unique_ptr<Spam> spam = std::make_unique<Spam>(Spam::token, 10); 
} 
5
Why am I not able to compile? 

non si riesce a compilare perché make_unique non è un amico di Spam.

Una soluzione alternativa per rendere make_unique un amico è spostare la creazione di unique_ptr in Spam.

class Spam { 
    ... 
private: 
    Spam(int) {} 

    static unique_ptr<Spam> create(int i) 
    { return std::unique_ptr<Spam>(new Spam(i)); } 
}; 

e quindi chiamare Foo invece.

void Foo() { 
    std::unique_ptr<Spam> spam = Spam::create(10); 
    ... 
} 
+0

Come è meglio di usare semplicemente 'new' direttamente in' Foo'? – ricab

+0

L'utilizzo di una tale costruzione consiste nel forzare l'utente dell'API in RAII. In questo modo è più difficile creare una perdita di memoria. Calling Foo eliminerà lo spam e lo spam verrà eliminato se un'eccezione viene lanciata successivamente all'interno di Foo. – Thomas

+0

Ma non stai forzando RAII: 'Foo' può ancora chiamare' new' se lo desidera dato che è un amico. E potrebbe decidere di usare 'unique_ptr' in ogni caso. Il punto è che 'make_unique' è preferibile a nuovo. Se hai intenzione di rilasciarlo, allora non vedo il beneficio della maggiore complessità in questo approccio. – ricab