2012-07-02 25 views
9

¿Cómo puedo tomar posesión de los datos std :: string char sin copiar y sin guardar el objeto source std :: string? (Quiero usar semántica móvil, pero entre diferentes tipos).¿Cómo puedo tomar posesión de un C++ std :: string char data sin copiar y mantener el objeto std :: string?

Utilizo el compilador C++ 11 Clang y Boost.

Básicamente quiero hacer algo equivalente a esto:

{ 
    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. 
} 

Para aclarar, necesito para deshacerse de std :: string: más por el camino. El código trata con datos binarios y binarios y debe manejarlo en el mismo formato. Y sí quiero los datos de std :: string, porque eso proviene de otra capa de código que funciona con std :: string.

Para dar más perspectiva donde me encuentro con ganas de hacerlo: por ejemplo, tengo un contenedor de socket asíncrono que debería poder tomar tanto std :: string como datos binarios del usuario para escribir. Ambas versiones de escritura "API" (tomando std :: string o row binary data) resuelven internamente la misma escritura (binaria). Necesito evitar cualquier copia ya que la cadena puede ser larga.

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 en este ejemplo para ilustrar la transferencia de la propiedad: cualquier otro enfoque con la misma semántica está bien para mí.

Me pregunto acerca de soluciones para este caso específico (con std :: string char buffer) y este tipo de problema con cadenas, flujos y similares generales: consejos para acercar los buffers entre string, stream, std containers y buffer tipos.

También apreciaría sugerencias y enlaces con enfoques de diseño C++ y técnicas específicas cuando se trata de pasar datos de búfer entre diferentes API's/types sin copiar. Menciono pero no uso streams porque soy inestable en ese tema.

+1

No se puede, porque no hay ninguna manera se puede recuperar el memoria de forma segura. En un momento dado deberías liberar el búfer, entonces ¿por qué no mantener la cuerda completamente baja, lo cual hace esto automáticamente? –

+0

Es mejor que escriba su propia implementación de cadena – Gigi

+4

'std :: unique_ptr ' sería lo único que permite algo similar. – ildjarn

Respuesta

9

¿Cómo puedo tomar posesión de los datos std :: string char sin copiarlos y sin guardar el objeto source std :: string? (Quiero usar semántica móvil, pero entre diferentes tipos)

No se puede hacer esto de forma segura.

Para una implementación específica, y en algunas circunstancias, podría hacer algo horrible como usar aliasing para modificar variables de miembros privados dentro de la cadena para engañar a la cadena haciéndola creer que ya no posee un búfer. Pero incluso si estás dispuesto a probar esto, no siempre funcionará. P.ej. Considere la optimización de cadenas pequeñas donde una cadena no tiene un puntero a algún buffer externo que contiene los datos, los datos están dentro del objeto string.


Si quiere evitar copia puede considerar cambiar la interfaz de scheduledWrite. Una posibilidad es algo así como:

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: Es bastante razonable querer eso, desafortunadamente simplemente no es posible, porque la clase no está diseñada para permitirlo. –

+0

@minsk: No sabes cómo se supone que se liberará el búfer. Como no hay un miembro 'release', no puedes lograr lo que quieres con' string'. –

+0

Esos son buenos puntos: optimización de cadenas pequeñas y saber cómo liberar otro buffer de implementación. ¿Qué pasa con std :: stringstream, puedo mover std :: string a std :: stringstream que expone sus buffers? Esos son objetos estándar, y std :: stringstream es consciente de std :: string .. Realmente me gustaría encontrar una solución que evite copiar y permita que parte del código funcione con cadenas :( – minsk

1

Puede usar polimorfismo para resolver esto. El tipo de base es la interfaz para su implementación de búfer de datos unificado. Entonces tendrías dos clases derivadas. Uno para std::string como fuente, y el otro usa su propia representación de datos.

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 Marqué esta respuesta porque es una solución y tengo que responderla. Pero evitaría este enfoque por varias razones, incluida la posible herencia virtual, tipo de seguridad, ma problemas de negocios; impone un tipo de datos (MyData) más adelante. Puede volverse muy engorroso. Tendré que tener algún tipo de acceso único en torno a data_src_, además de esto, tendré que poner MyData nuevo y ajustarlo (para pasarlo, incluso a otros hilos). Si tengo que ir con un contenedor, prefiero usar un enfoque menos intrusivo y más seguro sin virtual, sugerido en la primera respuesta por bames53 – minsk

2

Esta clase de tomar posesión de una cadena utilizando la semántica mover y 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; 

}; 

Ejemplo de uso:

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

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

// use charbuffer... 
+0

Por lo que tengo entendido, el charbuffer que se mueve mantendrá un shared_ptr vacío (el mismo que está valor predeterminado construido en el constructor copy-move) así que cuando el charbuffer movido sale del ámbito y se llama a su destructor, no ocurre nada. – Gigi

+0

Estás 100% en lo cierto, no estoy seguro de lo que estaba pensando ahora. :-P Perdón por el ruido. – ildjarn

+0

Ningún problema :) – Gigi

Cuestiones relacionadas