2012-07-02 13 views
9

Come posso acquisire la proprietà dei dati di std :: string char senza copiare e senza conservare l'oggetto std :: string source? (Voglio usare semantica commovente ma tra diversi tipi.)Come posso assumere la proprietà di un C++ std :: string char data senza copiare e mantenere l'oggetto std :: string?

Io uso il compilatore C++ 11 Clang e Boost.

Fondamentalmente voglio fare qualcosa di equivalente a questo:

{ 
    std::string s(“Possibly very long user string”); 
    const char* mine = s.c_str(); 

    // 'mine' will be passed along, 
    pass(mine); 

    //Made-up call 
    s.release_data(); 

    // 's' should not release data, but it should properly destroy itself otherwise. 
} 

Per chiarire, ho bisogno di sbarazzarsi di std :: string: a valle della strada. Il codice tratta sia di dati stringa che binari e dovrebbe gestirli nello stesso formato. E voglio i dati di std :: string, perché provengono da un altro livello di codice che funziona con std :: string.

Per dare più prospettiva dove cerco di farlo: ad esempio, ho un socket wrapper asincrono che dovrebbe essere in grado di prendere sia i dati std :: string che i dati binari dall'utente per la scrittura. Entrambe le versioni di scrittura "API" (che prendono i dati binari di std :: string o di riga) risolvono internamente la stessa scrittura (binaria). Devo evitare qualsiasi copia poiché la stringa potrebbe essere lunga.

WriteId  write(std::unique_ptr<std::string> strToWrite) 
{ 

    // Convert std::string data to contiguous byte storage 
    // that will be further passed along to other 
    // functions (also with the moving semantics). 
    // strToWrite.c_str() would be a solution to my problem 
    // if I could tell strToWrite to simply give up its 
    // ownership. Is there a way? 

    unique_ptr<std::vector<char> > dataToWrite= ?? 

    // 
    scheduleWrite(dataToWrite); 
} 

void scheduledWrite(std::unique_ptr< std::vecor<char> > data) 
{ 
    … 
} 

std :: unique_ptr in questo esempio per illustrare trasferimento della proprietà: qualsiasi altro approccio con la stessa semantica va bene per me.

Mi chiedo soluzioni a questo caso specifico (con std :: string char buffer) e questo tipo di problema con stringhe, stream e simili generali: suggerimenti per avvicinarsi ai buffer in movimento tra stringhe, stream, contenitori std e buffer tipi.

Avrei anche apprezzato suggerimenti e collegamenti con approcci di progettazione C++ e tecniche specifiche quando si tratta di passare i dati del buffer tra diverse API/tipi senza copiare. Menziono ma non uso i flussi perché sono tremolante su quell'argomento.

+1

Non è possibile, perché non v'è alcun modo per recuperare il memoria in modo sicuro. A un certo punto dovresti rilasciare il buffer, quindi perché non tenere la corda fino in fondo, cosa che fa automaticamente? –

+0

È meglio scrivere la propria implementazione di stringa – Gigi

+4

'std :: unique_ptr ' sarebbe l'unica cosa che consente qualcosa di simile. – ildjarn

risposta

9

Come posso acquisire la proprietà dei dati di std :: string char senza copiare e senza conservare l'oggetto std :: string object? (Voglio usare semantica commovente ma tra diversi tipi)

Non è possibile farlo in modo sicuro.

Per un'implementazione specifica, e in alcune circostanze, si potrebbe fare qualcosa di terribile come usare l'aliasing per modificare le variabili dei membri privati ​​all'interno della stringa in modo da indurre la stringa a pensare che non possieda più un buffer. Ma anche se sei disposto a provarlo, non funzionerà sempre. Per esempio. considera l'ottimizzazione della piccola stringa in cui una stringa non ha un puntatore a un buffer esterno che contiene i dati, i dati sono all'interno dell'oggetto stesso.


Se si desidera evitare la copia, è possibile prendere in considerazione la modifica dell'interfaccia su scheduledWrite. Una possibilità è qualcosa del tipo:

template<typename Container> 
void scheduledWrite(Container data) 
{ 
    // requires data[i], data.size(), and &data[n] == &data[0] + n for n [0,size) 
    … 
} 

// move resources from object owned by a unique_ptr 
WriteId write(std::unique_ptr< std::vector<char> > vecToWrite) 
{ 
    scheduleWrite(std::move(*vecToWrite)); 
} 

WriteId write(std::unique_ptr<std::string> strToWrite) 
{ 
    scheduleWrite(std::move(*strToWrite)); 
} 

// move resources from object passed by value (callers also have to take care to avoid copies) 
WriteId write(std::string strToWrite) 
{ 
    scheduleWrite(std::move(strToWrite)); 
} 

// assume ownership of raw pointer 
// requires data to have been allocated with new char[] 
WriteId write(char const *data,size_t size) // you could also accept an allocator or deallocation function and make ptr_adapter deal with it 
{ 
    struct ptr_adapter { 
     std::unique_ptr<char const []> ptr; 
     size_t m_size; 
     char const &operator[] (size_t i) { return ptr[i]; } 
     size_t size() { return m_size; } 
    }; 

    scheduleWrite(ptr_adapter{data,size}); 
} 
+1

@minsk: È abbastanza ragionevole volerlo, purtroppo non è possibile, perché la classe non è progettata per consentirlo. –

+0

@minsk: Non sai come dovrebbe essere rilasciato il buffer. Poiché non esiste un membro 'release', non è possibile ottenere ciò che si desidera con' string'. –

+0

Questi sono buoni punti: ottimizzazione delle stringhe piccole e sapendo come rilasciare un altro buffer di implementazione. Che dire di std :: stringstream, posso spostare std :: string in std :: stringstream che espone i suoi buffer? Questi sono entrambi oggetti std e std :: stringstream è a conoscenza di std :: string. Vorrei davvero trovare una soluzione che eviti la copia e permetta a parte del codice di lavorare con le stringhe :( – minsk

1

Per risolvere questo problema è possibile utilizzare il polimorfismo. Il tipo di base è l'interfaccia per l'implementazione del buffer di dati unificato. Quindi avresti due classi derivate. Uno per std::string come origine e l'altro utilizza la propria rappresentazione dei dati.

struct MyData { 
    virtual void * data() = 0; 
    virtual const void * data() const = 0; 
    virtual unsigned len() const = 0; 
    virtual ~MyData() {} 
}; 

struct MyStringData : public MyData { 
    std::string data_src_; 
    //... 
}; 

struct MyBufferData : public MyData { 
    MyBuffer data_src_; 
    //... 
}; 
+0

user315052 Ho contrassegnato questa risposta perché è una soluzione e grazie per la risposta, ma eviterei questo approccio per una serie di motivi, tra cui il possibile impatto dell'ereditarietà virtuale, digitare sicurezza, ma problemi di gestione; impone un tipo di dati (MyData) più avanti lungo la strada. Può diventare molto ingombrante. Dovrò avere una sorta di accesso univoco attorno a data_src_, in cima alla quale avrò i nuovi MyData e lo avvolgeremo (per passarlo anche ad altri thread). Se devo andare con un wrapper, preferisco usare un approccio meno intrusivo e più sicuro senza virtuale, suggerito in 1a risposta da bames53 – minsk

2

Questa classe assumere la proprietà di una stringa utilizzando la semantica spostare e shared_ptr:

struct charbuffer 
{ 
    charbuffer() 
    {} 

    charbuffer(size_t n, char c) 
    : _data(std::make_shared<std::string>(n, c)) 
    {} 

    explicit charbuffer(std::string&& str) 
    : _data(std::make_shared<std::string>(str)) 
    {} 

    charbuffer(const charbuffer& other) 
    : _data(other._data) 
    {} 

    charbuffer(charbuffer&& other) 
    { 
    swap(other); 
    } 

    charbuffer& operator=(charbuffer other) 
    { 
    swap(other); 
    return *this; 
    } 

    void swap(charbuffer& other) 
    { 
    using std::swap; 
    swap(_data, other._data); 
    } 

    char& operator[](int i) 
    { 
    return (*_data)[i]; 
    } 

    char operator[](int i) const 
    { 
    return (*_data)[i]; 
    } 

    size_t size() const 
    { 
    return _data->size(); 
    } 

    bool valid() const 
    { 
    return _data; 
    } 

private: 
    std::shared_ptr<std::string> _data; 

}; 

Esempio di utilizzo:

std::string s("possibly very long user string"); 

charbuffer cb(std::move(s)); // s is empty now 

// use charbuffer... 
+0

Per quanto ne so, il charbuffer che viene spostato terrà quindi un shared_ptr vuoto (lo stesso che è predefinito costruito nel costruttore copy-move) così quando il charbuffer spostato esce dall'ambito e il suo distruttore viene richiamato, non succede nulla. – Gigi

+0

Hai ragione al 100%, non sono sicuro di cosa stavo pensando ora. :-P Ci scusiamo per il rumore. – ildjarn

+0

Nessun problema :) – Gigi

Problemi correlati