2016-04-07 22 views
9

Vorrei eseguire un'iterazione su un array float pre-allocato con un contenitore personalizzato che non possiede i dati, ma che agisce su un segmento di esso. Esempio, nominando la classe contenitore LinhaSobre:Regole per la restituzione di riferimenti a oggetti falsi in C++

std::unique_ptr<float[]> data(new float[720]); 
... 
//creates container to iterate 26 floats starting from from data[12] 
    LinhaSobre cont(data.get()+12, 26); 
//sets those elements to 1.5 
    for(size_t i = 0; i < cont.size(); i++) 
     cont[i] = 1.5f; 

Ecco una possibile implementazione del operator[]:

//... 
//LinhaSobre has a member mem0 which is initialized 
//as a pointer to where the interval starts 
float & LinhaSobre::operator[] (size_t i) 
{ 
    return *(mem0+i); 
} 

Si noti che sto tornando un riferimento da LinhaSobre::operator[] a dati che non si possiede. Non dovrebbe interferire con la durata dei dati (costruttori, distruttori).

Ora desidero esporre lo data memorizzato con un altro motivo, std::array<float,4> e non puro float. Esempio, nominando la nuova classe LinhaSobre4f:

std::unique_ptr<float[]> data(new float[720]); 
... 
//creates container to iterate 4 array<float, 4> starting from from data[12] 
    LinhaSobre4f l(data.get()+(3*4), 4); 
//sets those elements to {1.5f, 2.5f, 3.5f, 4.5f}; 
    for(size_t i = 0; i < l.size(); i++) 
     l[i] = { {1.5f, 2.5f, 3.5f, 4.5f} }; 

Si noti che io tratto le voci come una matrice. Ciò porterebbe ad alcuni cambiamenti nella classe contenitore, la mia preoccupazione principale è con il operator[], ecco il codice completo della classe:

struct LinhaSobre4f 
{ 
    LinhaSobre4f(float * pos_begin, size_t size_): 
     pos0(pos_begin), 
     size_(size_){} 
    std::array<float, 4> & operator[](size_t i)const 
    { 
     std::array<float,4> * r = 
      reinterpret_cast<std::array<float,4>*> (pos0+(4*i)); 
     return *r; 
    } 
    size_t size()const 
    { 
     return size_; 
    } 
private: 
    float * pos0; 
    size_t size_; 
}; 

Il operator[] restituisce un riferimento a un blocco di memoria trattato come un std::array<float,4> che mai esisteva come tale, ma data la garanzia del layout di memoria std::array, funziona. Sono dubbioso su questo, va bene? (a parte l'allineamento della memoria, che garantirò). Sono autorizzato a esporre un oggetto come questo, semanticamente? Qual è il termine corretto per questo? (Ho usato oggetto falso nel titolo).

Here's a live demo of the example. Here's another (the other link sometimes fails)

+0

Una cosa che sicuramente mi distingue (e il compilatore ti avviserà di questo) è che in 'operator []' di 'LinhaSobre4f' stai restituendo un riferimento a una variabile temporanea - dovresti cambiare la firma di questo metodo per restituire per valore invece di riferimento. – ArchbishopOfBanterbury

+1

(Am I?) La variabile locale (temporanea) è un puntatore, e sto restituendo il dereferenziamento di esso, non un riferimento ad esso. – Kahler

+1

I * penso * che tu sia al sicuro, anche se non ho nulla a cui fare il backup. – vu1p3n0x

risposta

5

Il C++ standard (sto leggendo C++ 11) definisce un std::array come segue:

Le condizioni per un aggregato (8.5.1) sono soddisfatte.

Non è garantito che un std::array sia un POD. Lo standard C++ garantisce solo che si tratta di un aggregato di classe.

In base a ciò, credo che l'utilizzo di reinterpret_cast per convertire un array POD di float s in un std::array non sia un comportamento definito.

È probabile che funzioni con il compilatore, ma non è garantito che ciò sia portatile o legale.

+0

Cercando su di esso, ho incontrato il [std :: is_pod] (http://en.cppreference.com/w/cpp/types/is_pod), posso presumere che sia sicuro se [restituisce true a std :: array ] (http://ideone.com/ez6sX4)? – Kahler

+0

È sicuro se restituisce true con il compilatore corrente. Tuttavia, tieni presente che se restituisce true con il tuo compilatore corrente, non ci sono garanzie che restituirà true con qualsiasi altro compilatore, o qualsiasi versione futura o precedente del tuo compilatore. –

2

Si potrebbe creare una pianura vecchio reference_type:

struct LinhaSobre4f { 
    struct Ref { 
     Ref(float *m): m(m){}; 
     Ref &operator=(std::initializer_list<float> const &l) { 
      std::copy(l.begin(), l.end(), m); 
      return *this; 
     } 
    private: 
     float *m; 
    }; 
    Ref operator[](size_t i) { return m + 4 * i; } 
private: 
    float *m; 
}; 
+0

Capisco la delega, ma l'operatore '[]' ora restituisce un valore, non un riferimento ... Il mio desiderio era di essere in grado di operare direttamente nei dati, a parte che 'Ref' ora deve implementare ogni operazione desiderata. – Kahler

1

aggiunta alla risposta di Sam Varshavchik, si può essere interessati nel tipo span (formerly known as array_view).

Il tipo span è un'astrazione che fornisce una vista su una sequenza contigua di oggetti, la memorizzazione dei quali è di proprietà di un altro oggetto (maggiori dettagli in P0122R1, CppCoreGuidelines e Guidelines Support Library Review: span<T>).

Concettualmente, uno span è semplicemente un puntatore a qualche archivio e un conteggio degli elementi accessibili tramite quel puntatore. È così piccolo che può essere passato per valore.

An open source (solo intestazione), implementazione di riferimento è disponibile a https://github.com/Microsoft/GSL (attuazione assume generalmente una piattaforma che implementa C++ 14 di supporto. Esistono soluzioni specifiche per sostenere MSVC 2013 e 2015).

+0

Wow! Questo sembra essere fatto esattamente nel mio scenario! Tuttavia, è una classe di riferimento ... non un accesso diretto. Mi sto convincendo che la semantica C++ non copra che "tratti questo pezzo di memoria come (...)" direttamente. La classe helper deve codificare l'accesso in qualche modo. – Kahler

Problemi correlati