2013-02-22 15 views
6

Tutti,std :: unique_ptr cancellato funzione, initializer_list - guidato allocazione

Quando un'istanza di un array di widget utilizzando un formato di inizializzazione-list, un puntatore nuda che punta a un'istanza widget di membro variabile compila, ma dopo il cambio di std :: unique_ptr <> gcc restituisce un errore di compilazione relativo a una funzione cancellata.

$ uname -a

Linux .. 3.5.0-21-generiC# 32-Ubuntu SMP martedì 11 dicembre 18:51:59 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

$ g ++ - -version

g ++ (Ubuntu/Linaro 4.7.2-5ubuntu1) 4.7.2

Questo codice dà il seguente errore del compilatore:

#include <stdlib.h> 
#include <memory> 

class Widget 
{ 
public: 
    Widget() {} 
}; 

class W1 : public Widget 
{ 
public: 
    W1() {} 
}; 

class W2 : public Widget 
{ 
public: 
    W2() {} 
}; 

class WFactory 
{ 
public: 
    WFactory(const int i) : _w(new W1()) {} 
    WFactory(const char* s) : _w(new W2()) {} 

    ~WFactory() { _w.reset(nullptr); } 
    // ~WFactory() { delete _w; } <--- for naked ptr 

private: 
    // NOTE: does not compile 
    std::unique_ptr<Widget> _w; 
    // NOTE: does compile 
    // Widget* _w; 
}; 

int main() 
{ 
    std::unique_ptr<Widget> a(new W1()); // <--- compiles fine 

    WFactory wf[] { 4, "msg" };   // <--- compiler error using unique_ptr<> 
} 

errore:

$ g++ -o unique_ptr -std=c++11 -Wall unique_ptr.cpp 
unique_ptr.cpp: In function ‘int main()’: 
unique_ptr.cpp:36:30: error: use of deleted function ‘WFactory::WFactory(const WFactory&)’ 
unique_ptr.cpp:22:7: note: ‘WFactory::WFactory(const WFactory&)’ is implicitly deleted because the default definition would be ill-formed: 
unique_ptr.cpp:22:7: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Widget; _Dp = std::default_delete<Widget>; std::unique_ptr<_Tp, _Dp> = std::unique_ptr<Widget>]’ 
In file included from /usr/include/c++/4.7/memory:86:0, 
      from unique_ptr.cpp:2: 
/usr/include/c++/4.7/bits/unique_ptr.h:262:7: error: declared here 
unique_ptr.cpp:36:30: error: use of deleted function ‘WFactory::WFactory(const WFactory&)’ 
unique_ptr.cpp:36:14: warning: unused variable ‘wf’ [-Wunused-variable] 

Sono ad una perdita quanto a uno: la meccanica dietro le quinte che produce un fcxn soppressa; o più semplicemente, perché l'espressività di std :: unique_ptr <> sembra limitata rispetto a un ptr nudo.

La mia domanda è: Errore

  • pilota?
  • errore del compilatore?
  • posso ottenere il codice previsto per funzionare con qualche modifica?

Grazie.

Modifica 1

In base alle vostre risposte, che apprezzo, posso fare la seguente modifica al WFactory:

(contrassegnato codice come immorale)

class WFactory 
{ 
public: 
    WFactory(const WFactory& wf) 
    { 
     (const_cast<WFactory&>(wf)).moveto(_w); 
    } 

    WFactory(const int i) : _w(new W1()) {} 
    WFactory(const char* s) : _w(new W2()) {} 

    ~WFactory() { _w.reset(nullptr); } 

    void moveto(std::unique_ptr<Widget>& w) 
    { 
     w = std::move(_w); 
    } 
private: 
    std::unique_ptr<Widget> _w; 
}; 

e ora il programma viene compilato e eseguito. Apprezzo che gli standard abbiano scritto le specifiche per un motivo, quindi pubblico il mio risultato come una specializzazione in buona fede per il mio caso, dove vorrei davvero sottolineare l'unicità del ptr.

Edit 2

Sulla base di risposte di Jonathan, il seguente codice non sopprime l'implicito mossa ctor:

class WFactory 
{ 
public: 
    WFactory(const int i) : _w(new W1()) {} 
    WFactory(const char* s) : _w(new W2()) {} 

private: 
    std::unique_ptr<Widget> _w; 
}; 

Si noti che non v'è alcun ~WFactory() {..} a tutti.

Forse c'è ya-ans, ma ho scoperto che l'utilizzo di un'iterazione in stile C++ 11 su wf [] in Main() riporta l'errore no-copy-ctor-for-WFactory.Cioè:

int Main() 
.. 
    WFactory wf[] { 4, "msg" }; 

    for (WFactory iwf : wf) <---- compiler error again 
     // .. 

    for (unsigned i = 0; i < 2; ++i) <--- gcc happy 
     wf[i] // .. 
} 

Credo che sia evidente che la nuova iterazione C++ 11-stile sta facendo una copia dell'oggetto.

+0

L'inizializzazione tramite elenchi rinforzati richiede che gli oggetti siano formalmente copiabili, sfortunatamente. –

+0

Grazie, è interessante. Dov'è la copia? L'errore indica che WFactory deve essere copiabile. Non capisco. – JayInNyc

+0

È nelle specifiche della lingua. La copia in realtà non si verificherà nella pratica, ma la classe deve ancora avere un costruttore di copia accessibile. –

risposta

8

Secondo Paragrafo 8.5.1/2 del C++ 11 standard:

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer listare taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. [...]

Per ciascun elemento, quindi, copia-inizializzazione comporta la creazione di una temporanea del tipo di destinazione, che è poi usa per copiare-costruire l'elemento dell'array.

Tuttavia, la classe contiene un membro il cui tipo è un'istanza di unique_ptr, che non è copiabile. Ciò rende la tua classe non copiabile pure.

Inoltre, anche se è unique_ptrmobile, la classe non è, perché l'implicito generazione di un costruttore mossa dal compilatore viene soppressa dalla presenza di un distruttore esplicitamente definito. Se così non fosse (cioè, se hai definito esplicitamente un costruttore di mosse per la tua classe), l'inizializzazione della copia funzionerebbe (vedi 8.5/15).

provare a modificare la definizione di WFactory come segue vedere che:

class WFactory 
{ 
public: 
    WFactory(const int i) : _w(new W1()) {} 
    WFactory(const char* s) : _w(new W2()) {} 
    WFactory(WFactory&& f) : _w(std::move(f._w)) {} 
    ~WFactory() { _w.reset(nullptr); } 
private: 
    std::unique_ptr<Widget> _w; 
}; 

int main() 
{ 
    std::unique_ptr<Widget> a(new W1()); 
    WFactory wf[] { 4, "msg" };   // OK 
} 
+0

Vedere la modifica sopra. Grazie ancora. – JayInNyc

+0

[dcl.init]/15 "[_Note: _ L'inizializzazione della copia può invocare uno spostamento (12.8). - _end note] _" –

+0

@JonathanWakely: molto corretto. Fammi modificare Grazie. –

4

the mechanics behind the scenes that yields a deleted fcxn;

array possono essere inizializzate solo genere se il tipo è copiabile o mobile, e unique_ptr non è copiabile, quindi una classe con un membro unique_ptr non è possibile copiare per impostazione predefinita e il tuo tipo ha un distruttore definito dall'utente, che inibisce il costruttore di spostamento implicito, quindi anche il tuo tipo non è mobile.

or more simply, why the expressiveness of std::unique_ptr<> appears to be restricted compared w/ a naked ptr.

unique_ptr sta salvando da un bug grave. Con il puntatore nudo il tuo tipo è gravemente pericoloso e risulterà in un comportamento indefinito, perché non hai un costruttore di copia quindi il puntatore viene copiato e quindi cancellato due volte da due oggetti diversi. Boom, il tuo programma ha un comportamento indefinito. unique_ptr corregge la classe impedendone la copia, che è sicura e corretta.

È possibile farlo funzionare in diversi modi, il più semplice è rimuovere il distruttore definito dall'utente, che rende la classe mobile e l'inizializzazione dell'array verrà compilata.

Oppure se per un altro motivo è necessario un distruttore definito dall'utente, è comunque possibile farlo funzionare scrivendo un costruttore di spostamento definito dall'utente per spostare esplicitamente il membro _w.

Se per qualche motivo che non è possibile, è possibile farlo funzionare con l'aggiunta di un costruttore di default, quindi gli elementi dell'array possono essere di default costruito, e quindi spostare-assegnando loro:

class WFactory 
{ 
public: 
    WFactory() = default; 
    WFactory(const int i) : _w(new W1()) {} 
    WFactory(const char* s) : _w(new W2()) {} 

private: 
    std::unique_ptr<Widget> _w; 
}; 

int main() 
{ 
    WFactory wf[2]; 
    wf[0] = WFactory(4); 
    wf[1] = WFactory("msg"); 
} 

tuo La versione modificata è immorale e altamente dubbia, non si dovrebbe scartare lo const in questo modo e non si dovrebbe passare da un lvalue, specialmente non un valore const. Non andare li.Invece di cambiare come si utilizza la classe per evitare la necessità di copiarlo, o scrivere un costruttore di copia valida che fa un profondo copia dell'oggetto di proprietà:

class Widget 
{ 
public: 
    Widget() {} 
    virtual std::unique_ptr<Widget> clone() const = 0; 
}; 

class W1 : public Widget 
{ 
public: 
    W1() {} 
    virtual std::unique_ptr<Widget> clone() const 
    { return std::unique_ptr<Widget>(new W1(*this)); } 
}; 

class W2 : public Widget 
{ 
public: 
    W2() {} 
    virtual std::unique_ptr<Widget> clone() const 
    { return std::unique_ptr<Widget>(new W2(*this)); } 
}; 

class WFactory 
{ 
public: 
    WFactory(const int i) : _w(new W1()) {} 
    WFactory(const char* s) : _w(new W2()) {} 
    WFactory(const WFactory& w) : _w(w._w->clone()) {} 
    // ... 

L'approccio migliore è quello di rendere mobile la classe, e un buon modo per farlo è seguire lo rule of zero

+0

Mi piace il fatto che la mia modifica sia immorale ;-) Questo mi ha fatto sorridere (davvero). – JayInNyc

+0

"Con il puntatore nudo il tuo tipo è gravemente pericoloso e risulterà in un comportamento indefinito, perché non hai un costruttore di copia in modo che il puntatore venga copiato e quindi cancellato due volte da due oggetti diversi.Prob, il tuo programma ha un comportamento indefinito. corregge la tua classe impedendone la copia, che è sicura e corretta. " -- Quindi siamo d'accordo. Questa è l'ispirazione per la mia domanda iniziale. – JayInNyc

+0

Per la mia applicazione non è possibile chiamare in serie wf [0] = ..., wf [1] = ... wf [] {..} è il meccanismo che I (attualmente) richiede. – JayInNyc

Problemi correlati