Come passare il puntatore const const oggetto utilizzando unique_ptr

Voglio passare un unique_ptr a una funzione di supporto, e voglio fare in modo che la funzione di supporto né modifica il puntatore, né l’oggetto appuntito. Senza unique_ptr, la soluzione è

void takePtr(AClass const * const aPtr) {
  //Do something with *aPtr. 
  //We cannot change aPtr, not *aPtr. 
}

(Beh, tecnicamente, AClass const * aPtr è sufficiente. E posso chiamare questo con

AClass * aPtr2 = new AClass(3);
takePtr(aPtr2);

Voglio invece usare unique_ptr, ma non riesce a capire come scrivere questo. Ho provato

void takeUniquePtr(unique_ptr<AClass const> const & aPtr) {
  //Access *aPtr, but should not be able to change aPtr, or *aPtr. 
}

Quando chiamo con

unique_ptr<AClass> aPtr(new AClass(3));
takeUniquePtr(aPtr);

non compilare. L’errore che vedo è

testcpp/hello_world.cpp:141:21: error: invalid user-defined conversion from std::unique_ptr<AClass>’ to const std::unique_ptr<const AClass>&’ [-fpermissive]

Non la conversione da unique_ptr<AClass> per unique_ptr<AClass const> essere automatico? Quello che mi manca qui?

A proposito, se cambio unique_ptr<AClass const> const & aPtr per unique_ptr<AClass> const & aPtr nella definizione della funzione, lo compila, ma poi posso funzioni di chiamata come aPtr->changeAClass(), che non voglio permettere.

  • Perché non passare un const reference, quindi? (invece di un unique_ptr)
  • Marshall Clow: mi capita di avere unique_ptrs il codice. Posso dereferenziare il puntatore e pass come const riferimento, ma che non è l’ideale, dato il codice di base con cui sto lavorando. Inoltre, a volte, il unique_ptr potrebbe puntare a NULL nel mio caso (non per la gestione di qualsiasi oggetto), in modo const reference non funziona in questo caso.
  • In questo caso, vorrei solo passare un const AClass *. Nessuna necessità per il chiamato per sapere che è un unique_ptr. Perché un paio che ben?
  • La tua domanda non fare un sacco di senso. Se si passa un unique_ptr per l’aiuto, sarà per definizione libera l’oggetto quando è fatto con esso-che è il punto di unique_ptr. Se non si desidera che, perché non solo passare un regolare (const) puntatore?
  • si noti che passa un riferimento a unique_ptr, in modo che l’oggetto continuerà a vivere quando il figurante è fatto.
  • Marshall Clow: potrei aver bisogno di farlo. .get() il unique_ptr, e passare il puntatore raw come puntatore const const oggetto.
  • Ma un’altra domanda rimane: Perché il compilatore non è in grado di gettare da unique_ptr<A> per unique_ptr<A const>? (Implicito) non-const const conversione accade tutto il tempo! 🙂
  • Sì, è una cosa – T e const T ma sono molto diversi da class<T> e class<const T>. Nel primo caso, le due classi sono le stesse, uno solo è segnato const (c’è anche un set di template metafunctions per la conversione add_const e remove_const. Nel secondo caso, non c’è modo che il compilatore può essere sicuri che le classi sono in qualche modo collegate (tranne il nome). Posso specializzarsi un modello per i tipi di const e di essere completamente diverso da quello per la non-const tipi.
  • Basta passare un riferimento all’oggetto const. Non ha molto senso passare un puntatore a tutti (meno che non ci sia qualche motivo, non deve essere un puntatore, e non hai detto che non c’è)



2 Replies
  1. 28

    Puntatori intelligenti sono per la gestione di proprietà e di vita che permette (tra le altre cose) di sicuro il trasferimento di proprietà in giro per le varie parti del nostro codice.

    Quando si passa un const unique_ptr<T>& di una funzione (irrilevante se T è const o non), cosa significa in realtà è che la funzione promette di non modificare mai il unique_ptr stesso (ma potrebbe ancora modificare la punta di un oggetto se T non è const) ie. non ci sarà alcun trasferimento di proprietà di sorta. Stai usando solo il unique_ptr come un inutile involucro intorno ad un nudo puntatore.

    Così, come @MarshallClow suggerito in un commento, si dovrebbe sbarazzarsi di il wrapper e passare una naked puntatore o un riferimento diretto. La cosa bella è che il tuo codice è ora semanticamente chiaro (la funzione di firma, si afferma chiaramente che non si scherza con la proprietà, che è stato non immediatamente evidente con un const unique_ptr<...>&) e si risolve il “constification” problema allo stesso tempo!

    E cioè:

    void someFunction(const AClass* p) { ... }
    
    std::unique_ptr<AClass> ptr(new AClass());
    someFunction(ptr.get());

    Edit: di indirizzo secondario domanda “perché non il compilatore mi faccia … cast unique_ptr<A> per unique_ptr<A const>?“.

    In realtà, si può spostare un unique_ptr<A> per un unique_ptr<A const>:

    std::unique_ptr<A> p(new A());
    std::unique_ptr<const A> q(std::move(p));

    Ma come si può vedere questo comporta un trasferimento di proprietà da p per q.

    Il problema con il tuo codice è che si sta passando un (riferimento a) unique_ptr<const A> di una funzione. Dal momento che c’è una discrepanza con unique_ptr<A>, per farlo funzionare il compilatore deve creare un’istanza di un temporaneo. Ma a meno di non trasferire la proprietà manualmente utilizzando std::move, il compilatore cercherà di copia tuo unique_ptr e non può fare che da unique_ptr esplicitamente proibisce.

    Notare come il problema scompare se si sposta il unique_ptr:

    void test(const std::unique_ptr<const int>& p) { ... }
    
    std::unique_ptr<int> p(new int(3));
    test(std::move(p));

    Il compilatore è ora in grado di costruire una temporanea unique_ptr<const A> e spostare l’originale unique_ptr<A> senza rompere le vostre aspettative (poiché è ormai chiaro che si desidera spostare, copiare).

    Così, la radice del problema è che unique_ptr ha solo spostare la semantica non copia semantica, ma c’è bisogno di copia semantica per creare una temporanea e ancora mantenere la proprietà in seguito. L’uovo e la gallina, unique_ptr solo che non è stato progettato in quel modo.

    Se ora a considerare shared_ptr che ha copia semantica, il problema scompare.

    void test(const std::shared_ptr<const int>& p) { ... }
    
    std::shared_ptr<int> p(new int(3));
    test(p);
    //^^^^^ Works!

    Il motivo è che il compilatore è ora in grado di creare una temporanea std::shared_ptr<const int> copia (automaticamente casting da std::shared_ptr<int>) e associare temporanea per un const di riferimento.

    Credo che questo più o meno lo copre, anche se la mia spiegazione manca standardese lingo e forse non è così chiaro come dovrebbe essere. 🙂

    • Nel secondo paragrafo, come hai detto, la funzione di “promesse” per non modificare l’oggetto. Credo che la domanda è perché non il compilatore mi permette di effettuare una garanzia in giro che, da che mi permette di cast unique_ptr<A> per unique_ptr<A const>? Sto indovinando che mi manca qualcosa qui. La maggior parte del tempo, C++ non consente le cose per un buon motivo, no? 🙂
    • Quello che volevo dire con object era il unique_ptr sé, non necessariamente la punta di un oggetto. Mi rendo conto che questo non risolve la questione nel tuo commento, ma ho pensato comunque di chiarire (e vorrei modificare la mia domanda di conseguenza). Come per la domanda, beh, sono d’accordo che “intuitivamente” si dovrebbe essere in grado di lanciare da unique_ptr<A> per unique_ptr<const A> (ma ovviamente non il contrario). Ma come si dice, C++, probabilmente ha una buona ragione per questo, dobbiamo solo trovare. 😉
    • Ho modificato la mia risposta per risolvere il problema. TLDR; non è possibile creare una copia temporanea di un unique_ptr, perché ha solo spostare la semantica.
    • syam: Che senso. Grazie per aver dedicato del tempo a spiegare questo. E ‘ abbastanza chiaro. L’unico pezzo mancante nella mia mente è che se davvero c’è un’istanza di una temporanea succedendo qui? Ho stampato qualcosa nel costruttore della classe, e poi passato shared_ptr<A> di una funzione accettare shared_ptr<A const> e sembra che non vi è alcun costruttore chiamato. Questo è coerente con la vostra comprensione?
    • stampato qualcosa nel costruttore della classe” => se vuoi dire la tua punta di classe, quindi sì, è coerente: temporaneo è un shared_ptr oggetto, la punta per oggetto rimane la stessa per entrambi originali shared_ptr<A> e temporanea shared_ptr<const A>. Per osservare la temporanea devi hackerare l’origine di shared_ptr e aggiungere una traccia nei suoi costruttori.
  2. 2

    Arrivati a questa vecchia domanda su const puntatori intelligenti.
    Risposte di cui sopra ignorare la semplice soluzione di modello.

    Il modello molto semplice opzione (opzione a)

    template<class T>
    void foo(const unique_ptr<T>& ptr) {
        //do something with ptr
    }

    questa soluzione permette di avere tutte le possibili opzioni di unique_ptr essere inviato a pippo:

    1. const unique_ptr<const int>
    2. unique_ptr<const int>
    3. const unique_ptr<int>
    4. unique_ptr<int>

    Se appositamente per evitare che per qualche motivo 3 e 4 di cui sopra, aggiungere const T:

    Accettare solo const/non-const unique_ptr const! (opzione b)

    template<class T>
    void foo(const unique_ptr<const T>& ptr) {
        //do something with ptr
    }

    Lato nota 1

    Si può sovraccaricare l’opzione “a” e “opzione b” se per ottenere un comportamento diverso per i casi in cui si può o non può alterare la punta di valore.

    Lato nota 2

    Se non si desidera apportare modifiche alla sottolineato il valore di questa funzione (non sia mai! qualunque sia il tipo di parametro si ottiene!) — non sovraccaricare.

    Con l’opzione “b”, compilatore non permettono di modificare il valore di punto. Lavoro fatto!

    Se si desidera il supporto di tutti e 4 i casi, vale a dire opzione “a”, la funzione può ancora “accidentalmente” modificare il valore possiamo scegliere, ad esempio,

    template<class T>
    void foo(const unique_ptr<T>& ptr) {
        *ptr = 3;
    }

    tuttavia, questo non dovrebbe essere un problema se almeno un chiamante ha T che è in realtà const, il compilatore non come in questo caso e vi aiutano a ottenere il problema.

    Si può aggiungere ad un chiamante in unit test, qualcosa come:

        foo(make_unique<const int>(7)); //if this line doesn't compile someone
                                        //is changing value inside foo which is
                                        //not allowed!
                                        //do not delete the test, fix foo!

    Frammento di codice: http://coliru.stacked-crooked.com/a/a36795cdf305d4c7

Lascia un commento