2013-07-26 5 views
6

Supponiamo che ho una classe che gestisce un puntatore a un buffer interno:Spostare la semantica con un puntatore a un buffer interno

class Foo 
{ 
    public: 

    Foo(); 

    ... 

    private: 

    std::vector<unsigned char> m_buffer; 
    unsigned char* m_pointer; 
}; 

Foo::Foo() 
{ 
    m_buffer.resize(100); 
    m_pointer = &m_buffer[0]; 
} 

Ora, supponiamo che io anche hanno attuato correttamente roba regola-of-3 tra cui una copia costruttore che copia il buffer interno, e quindi riassegna il puntatore alla nuova copia del buffer interno:

Foo::Foo(const Foo& f) 
{ 
    m_buffer = f.m_buffer; 
    m_pointer = &m_buffer[0]; 
} 

Se anche implementare semantica spostare, è sicuro di basta copiare il puntatore e spostare il buffer?

Foo::Foo(Foo&& f) : m_buffer(std::move(f.m_buffer)), m_pointer(f.m_pointer) 
{ } 

In pratica, so che questo dovrebbe funzionare, poiché il costruttore std::vector mossa è solo muovendo il puntatore interno - non è in realtà una ridistribuzione niente di così m_pointer ancora punta a un indirizzo valido. Tuttavia, non sono sicuro che lo standard garantisca questo comportamento. La semantica di spostamento std::vector garantisce che non si verificherà alcuna riallocazione e quindi tutti i puntatori/iteratori del vettore sono validi?

+0

So che 'swap' garantisce che i puntatori saranno ancora validi, ma non sono sicuro delle mosse. –

+1

C'è stata una discussione molto lunga su questi problemi su una [domanda correlata] (http://stackoverflow.com/q/17730689/1782465). Credo che la conclusione sia che in un agente di movimento, il buffer è garantito per essere appena trasferito. In un'operazione di assegnazione di spostamento, può accadere (in base alle proprietà di allocatore) che il buffer verrà copiato. – Angew

+3

Non dovresti fare 'f.m_pointer = nullptr;' nel corpo del costruttore di move? Perché indicare un indirizzo che non è i suoi fratelli? Se hai un accoppiamento tra due variabili che è piuttosto stretto, direi di tenerlo così, invece di fare un cross coupling. – legends2k

risposta

4

Vorrei fare ancora &m_buffer[0] semplicemente per non dover fare queste domande. Ovviamente non è ovviamente intuitivo, quindi non farlo. E così facendo non hai nulla da perdere. Win-win.

Foo::Foo(Foo&& f) 
    : m_buffer(std::move(f.m_buffer)) 
    , m_pointer(&m_buffer[0]) 
{} 

mi sento a mio agio con esso soprattutto perché è una m_pointervista negli Stati m_buffer, piuttosto che strettamente un membro a pieno titolo.

Che cosa fa tutti i tipi di domanda ... perché è lì? Non puoi esporre una funzione membro per darti &m_buffer[0]?

+0

Bene, suppongo che altre funzioni membro possano far avanzare il puntatore o qualcosa del genere. –

+1

Sì, potrebbe essere il caso. Se questo è vero, se questo 'm_pointer' è veramente un _cursore_ il cui stato è previsto che cambi indipendentemente dal buffer stesso, allora questa risposta è sbagliata. Ma poi proporrei di memorizzare un indice invece di un puntatore. Può essere. E puoi almeno copiare l'indice, allora. –

2

Un modo migliore per farlo potrebbe essere:

class Foo 
{ 

    std::vector<unsigned char> m_buffer; 
    size_t m_index; 

    unsigned char* get_pointer() { return &m_buffer[m_index]; 
}; 

Per esempio, piuttosto che memorizzare un puntatore ad un elemento vettore, memorizzare l'indice di esso. In questo modo sarà immune alla copia/ridimensionamento del backstore dei vettori.

+0

Questa è una buona risposta, ma in realtà non risponde a quello che ho chiesto. Sono consapevole che usare gli indici è più sicuro, ma mi piacerebbe sapere se la semantica di spostamento di 'std :: vector' non valida iteratori/puntatori? – Channel72

5

Non commenterò il codice dell'OP. Tutto quello che sto facendo è aswering questa domanda:

Does semantica std :: vector mossa garantiscono che si verifichi alcuna riallocazione, e quindi tutti i puntatori/iteratori al vettore sono validi?

Sì per il costruttore di movimento. Ha una complessità costante (come specificato in 23.2.1/4, tabella 96 e nota B) e per questo motivo l'implementazione non ha altra scelta che rubare la memoria dall'originale vector (quindi non si verifica la riallocazione della memoria) e lo svuotamento dell'originale vector .

No per l'operatore di assegnazione spostamento. Lo standard richiede solo la complessità lineare (come specificato nello stesso paragrafo e nella tabella sopra menzionata) perché a volte è necessaria una riallocazione. Tuttavia, in alcuni casi, potrebbe avere una complessità costante (e nessuna riallocazione viene eseguita) ma dipende dall'allocatore. (È possibile leggere l'esposizione excelent mossi vector s da Howard Hinnanthere.)

1

Il caso di costruzione movimento è garantita mossa buffer da un contenitore all'altro, quindi dal punto di vista della appena creata oggetto, l'operazione va bene.

D'altra parte, si dovrebbe fare attenzione con questo tipo di codice, poiché l'oggetto donatore è lasciato con un vettore vuoto e un puntatore che si riferisce al vettore in un oggetto diverso. Ciò significa che dopo essere stato spostato dal tuo oggetto è in uno stato fragile che potrebbe causare problemi se qualcuno accede all'interfaccia e ancora più importante se il distruttore tenta di utilizzare il puntatore.

Mentre in generale non ci sarà alcun uso del tuo oggetto dopo essere stato spostato da (il presupposto è che per essere vincolato da un riferimento di rvalore deve essere un rvalore), il fatto è che puoi muoverti fuori un lvalue per casting o usando std::move (che è fondamentalmente un cast), nel qual caso il codice potrebbe effettivamente tentare di usare il tuo oggetto.

+1

Una soluzione alla fragilità è azzerare il valore di 'm_pointer' della sorgente di rvalue, ma mi piace usare un tipo per farlo per me, ad es. ['tidy_ptr'] (https://gitorious.org/redistd/redistd/blobs/master/include/redi/tidy_ptr.h), che consente quindi a' Foo (Foo &&) = default; 'di fare la cosa giusta. –

+0

@JonathanWakely: non volevo approfondire i dettagli, in quanto non so in particolare come viene utilizzato il componente. Il bit importante è che il distruttore deve essere in grado di gestire il caso di un vettore vuoto e ignorare il puntatore o impostarlo su 0 e quindi ignorarlo. –