¿Por qué no podemos cambiar el puntero de datos de std :: vector?

Dec 30 2020

Tengo una matriz char* sourcey un vector std::vector<char> target. Me gustaría hacer que el vector targetapunte sourceen O (1), sin copiar los datos.

Algo en esta línea:

#include <vector>

char* source = new char[3] { 1, 2, 3 };
std::vector<char> target;
target.resize(3);

target.setData(source); // <- Doesn't exist
// OR
std::swap(target.data(), source); // <- swap() does not support char*
delete[] source;

¿Por qué no es posible cambiar manualmente a dónde apunta un vector? ¿Existe algún problema específico inmanejable que surgiría si esto fuera posible?

Respuestas

7 anatolyg Dec 30 2020 at 17:39

La vectorclase C ++ admite la adición y eliminación de elementos, con un orden consecutivo garantizado en la memoria. Si pudiera inicializar su vectorcon el búfer de memoria existente y agregarle suficientes elementos, se desbordaría o requeriría reasignación.

La interfaz de vectorasume que administra su búfer interno, es decir, puede asignarlo, desasignarlo, redimensionarlo cuando quiera (dentro de las especificaciones, por supuesto). Si necesita algo que no esté autorizado para administrar su búfer, no puede usarlo vector: use una estructura de datos diferente o escriba una usted mismo.

Puede crear un vectorobjeto copiando sus datos (usando un constructor con dos punteros o assign), pero obviamente esto no es lo que desea.

Alternativamente, puede usar string_view, que se ve casi o tal vez exactamente lo que necesita.

3 NutCracker Dec 30 2020 at 19:23

std::vectorse considera el propietario del búfer subyacente. Puede cambiar el búfer, pero este cambio provoca la asignación, es decir, hacer una copia del búfer de origen que no desea (como se indica en la pregunta).

Podrías hacer lo siguiente:


#include <vector>

int main() {
    char* source = new char[3] { 1, 2, 3 };
    std::vector<char> target;
    target.resize(3);
    target.assign(source, source + 3);
    delete[] source;
    return 0;
}

pero de nuevo std::vector::assign:

Reemplaza el contenido con copias de aquellos en el rango [primero, último).

Entonces se vuelve a realizar la copia. No puedes alejarte de él mientras lo usas std::vector.

Si no desea copiar datos, debe usar std::spandesde C ++ 20 (o crear su propio intervalo) o usar std::string_view(que parece adecuado para usted ya que tiene una matriz de chars).

1ra opción: Usando std::string_view

Dado que está limitado a C ++ 17, std::string_viewpodría ser perfecto para usted. Construye una vista de los primeros 3 caracteres de la matriz de caracteres comenzando con el elemento señalado por source.

#include <iostream>
#include <string_view>

int main() {
    char* source = new char[3] { 1, 2, 3 };

    std::string_view strv( source, 3 );

    delete[] source;

    return 0;
}

2da opción: Usando std::spandesde C ++ 20

std::spanproviene de C ++ 20, por lo que puede que no sea la forma más perfecta para usted, pero es posible que le interese qué es y cómo funciona. Puede pensar std::spanen una versión un poco generalizada de std::string_viewporque es una secuencia contigua de objetos de cualquier tipo, no solo caracteres. El uso es similar al de std::string_view:

#include <span>
#include <iostream>

int main() {
    char* source = new char[3] { 1, 2, 3 };

    std::span s( source, 3 );

    delete[] source;

    return 0;
}

Tercera opción: tu propio tramo

Si está limitado a C ++ 17, puede pensar en crear su propia spanestructura. Todavía puede ser una exageración, pero déjeme mostrarle (por cierto, eche un vistazo a esta respuesta más elaborada ):

template<typename T>
class span {
   T* ptr_;
   std::size_t len_;

public:
    span(T* ptr, std::size_t len) noexcept
        : ptr_{ptr}, len_{len}
    {}

    T& operator[](int i) noexcept {
        return *ptr_[i];
    }

    T const& operator[](int i) const noexcept {
        return *ptr_[i];
    }

    std::size_t size() const noexcept {
        return len_;
    }

    T* begin() noexcept {
        return ptr_;
    }

    T* end() noexcept {
        return ptr_ + len_;
    }
};

int main() {
    char* source = new char[3] { 1, 2, 3 };

    span s( source, 3 );

    delete[] source;

    return 0;
}

Entonces, el uso es el mismo que con la versión de C ++ 20 de std::span.