2012-04-13 19 views
6

Ho scritto una funzione che carica i byte da un file e restituisce una struttura FileData che contiene il buffer di byte e la lunghezza del buffer.Utilizzo di puntatori intelligenti in una struct o classe

Desidero cancellare il buffer non appena viene consumato e gettato fuori dall'ambito.

Ho difficoltà a farlo compilare a causa di vari errori di trasmissione. Inoltre, non sono sicuro che il buffer venga spostato correttamente anziché copiato. Non mi interessa che la struttura FileData venga copiata, poiché al massimo è composta da 16 byte.

In generale, come si usano i puntatori intelligenti come campi di classe/struct? È qualcosa che potresti fare?

Questa è una domanda un po 'nebulosa, lo so, ma dal momento che sto avendo alcune difficoltà concettuali con i puntatori intelligenti in generale, spero che questo esempio mi aiuti nella giusta direzione.

Ecco quello che ho finora:

struct FileData 
{ 
    unique_ptr<char[]> buf; 
    unsigned int len; 
}; 

FileData LoadFile(string filename) 
{ 
    ifstream str; 
    str.open(filename, ios::binary); 

    str.seekg(0, ios::end); 
    auto len = str.tellg(); 
    str.seekg(0, ios::beg); 

    char* buf = new char[len]; 

    str.read(buf, len); 
    str.close(); 

    FileData d = { unique_ptr<char[]>(buf), len }; 

    return d; 
} 

Edit: Dal momento che alcune persone sono curiosi di sapere il messaggio di errore che ottengo con questo codice corrente, eccolo:

error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>' 
+0

Il tuo problema è che non fornisci alcun dettaglio specifico sui messaggi di errore. Come possiamo identificarli altrove? – Puppy

+0

@DeadMG Ho pensato che sarebbe stato chiaro che ci sono problemi con il codice, dal momento che ho fatto notare che non sono sicuro che questo sia il modo corretto di usare puntatori intelligenti e spostare la semantica. Mi piacerebbe che il codice facesse più che compilare; Mi piacerebbe che fosse corretto e idiomatico. Tuttavia, ho aggiornato la domanda con il messaggio di errore. –

+0

L'errore che stai ottenendo è che stavi cercando di copiare un unique_ptr, devi usare std :: move. Potresti aver usato un shared_ptr e dichiarato il tuo deallocator, ma la soluzione vettoriale è molto più pulita. – pstrjds

risposta

5

Il codice va bene, tranne per un piccolo dettaglio:

struct FileData 
{ 
    unique_ptr<char[]> buf; 
    <del>unsigned int</del> <ins>streamoff</ins> len; 
}; 

Il motivo per cui non viene compilato per voi è che il compilatore non implementa ancora la generazione automatica dei membri speciali del movimento. In un compilatore C++ completamente conforme 11 tua FileData sarebbe comportarsi come se:

struct FileData 
{ 
    unique_ptr<char[]> buf; 
    streamoff len; 

    FileData(FileData&&) = default; 
    FileData& operator=(FileData&&) = default; 
    FileData(const FileData&) = delete; 
    FileData& operator=(const FileData&) = delete; 
    ~FileData() = default; 
}; 

Il costruttore mossa default è sufficiente spostare costrutti ogni membro (e allo stesso modo per l'assegnazione mossa default).

Quando si restituisce d da LoadFile, si verifica uno spostamento implicito che si associa al costruttore di mosse implicitamente predefinito.

L'uso di vector<char> o string come altri hanno suggerito funzionerà anche. Ma non c'è niente di sbagliato nel tuo codice per quanto riguarda C++ 11.

Oh, potrei modificarlo in questo modo: mi piace avere le mie risorse di proprietà il più rapidamente possibile:

FileData LoadFile(string filename) 
{ 
    ifstream str; 
    str.open(filename, ios::binary); 

    str.seekg(0, ios::end); 
    auto len = str.tellg(); 
    str.seekg(0, ios::beg); 

    FileData d = {unique_ptr<char[]>(new char[len]), len}; 

    str.read(d.buf.get(), d.len); 
    str.close(); 

    return d; 
} 

Se è necessario definire in modo esplicito i membri mossa FileData, dovrebbe assomigliare:

struct FileData 
{ 
    unique_ptr<char[]> buf; 
    streamoff len; 

    FileData(FileData&& f) 
     : buf(std::move(f.buf)), 
      len(f.len) 
     { 
      f.len = 0; 
     } 

    FileData& operator=(FileData&& f) 
    { 
     buf = std::move(f.buf); 
     len = f.len; 
     f.len = 0; 
     return *this; 
    } 
}; 

Oh, che mi porta ad un altro punto. I membri di spostamento predefiniti non sono esattamente corretti in quanto non impostano len su 0 nella sorgente. Dipende dalla tua documentazione se questo è un bug o no. ~FileData() non richiede len per riflettere la lunghezza del buffer. Ma altri clienti potrebbero. Se si definisce un spostato da FileData come non dotato di un affidabile len, i membri di spostamento predefiniti vanno bene, altrimenti non lo sono.

+0

'FileData (FileData &&) = default;' - è una sintassi abbreviata che funziona su alcuni compilatori? Quando provo a creare esplicitamente un costruttore di mosse, si lamenta che 'operator =' non è accessibile. Hmm ... (VC++ 11, tra l'altro) –

+0

@ReiMiyasaka: Sì, la sintassi '= default' è un modo per richiedere esplicitamente cosa in C++ 98/03 si farebbe riferimento come membro speciale generato implicitamente . Funziona per il ctor predefinito, il copy ctor, il copy assignment, il move ctor, lo spostamento del compito e il dtor. Ma potrebbe non essere ancora implementato per te (non ho familiarità con VC++ 11). Queste funzionalità sono state standardizzate in ritardo nel gioco, quindi è comprensibile che il tuo compilatore potrebbe non averle ancora. La sintassi "= delete" equivale approssimativamente a dichiararla privata e non a definirla. Non sono sicuro di cosa stia succedendo con il tuo agente di movimento esplicito. –

2

io probabilmente usare un std::vector invece di un std:::unique_ptr<char[]>, se non ti dispiace il std::vector la copia quando si torna la FileData:

0.123.
struct FileData 
{ 
    vector<char> buf; 
}; 

FileData LoadFile(string filename) 
{ 
    ifstream str; 
    str.open(filename, ios::binary); 

    str.seekg(0, ios::end); 
    auto len = str.tellg(); 
    str.seekg(0, ios::beg); 

    FileData d; 
    d.buf.resize(len); 

    str.read(&(d.buf)[0], len); 
    str.close(); 

    return d; 
} 

alternativa, per evitare la copia, il chiamante può passare in un FileData come parametro funzione invece di un valore di ritorno:

struct FileData 
{ 
    vector<char> buf; 
}; 

void LoadFile(string filename, FileData &data) 
{ 
    ifstream str; 
    str.open(filename, ios::binary); 

    str.seekg(0, ios::end); 
    auto len = str.tellg(); 
    str.seekg(0, ios::beg); 

    data.buf.resize(len); 

    str.read(&(data.buf)[0], len); 
    str.close(); 
} 
+0

Questo non porta alla copia dei dati del file stesso "array", o non implementa la semantica del movimento C++ 11? Non sto cercando di essere polemico, solo curioso. – pstrjds

+0

In tal caso non avrei nemmeno bisogno della struttura FileData, in modo da rispondere a metà della mia domanda. Grazie. Immagino che le prestazioni non siano poi così male con il ridimensionamento di un vettore? –

+1

@Rei: non verrà ridimensionato nell'esempio fornito. Perché è inizialmente vuoto, è più simile a "dimensione": P e non è più lento di allocare la memoria da soli. – Puppy

-1

ne dite di usare std :: string come buffer. E 'tutto il comportamento desiderato:

  • riferimento contato piuttosto che copiato
  • scompare una volta fuori del campo di applicazione
  • detengono una quantità arbitraria di byte arbitrario

La gente giù votare questo perché il suo non l'uso previsto originale della stringa; forse derivare una classe (o avvolgere) e lo chiamano 'tampone'

+0

Penso che funzionerebbe, ma ancora una volta non mi aiuta a capire come usare i puntatori intelligenti in altre situazioni simili. –

+0

non so unique_ptr (io uso boost) ma unique_ptr mi sembra sbagliato, di solito i ptr di array hanno la loro classe. Vorrei uniq_ptr . Fondamentalmente dovrebbe funzionare – pm100

+0

Quando ha fatto il conteggio di 'std :: string'?O è specifico per l'implementazione in diversi compilatori? –

Problemi correlati