Cambiar el tamaño de un C++ std::vector sin inicializar datos [duplicate]

6 minutos de lectura

avatar de usuario
usuario984228

Con los vectores, se puede suponer que los elementos se almacenan de forma contigua en la memoria, lo que permite que el rango [&vec[0]&vec[vec.capacity()) to be used as a normal array. E.g.,

vector<char> buf;
buf.reserve(N);
int M = read(fd, &buf[0]N);

Pero ahora el vector no sabe que contiene METRO bytes de datos, agregados externamente por leer(). Yo sé eso vector::redimensionar() establece el tamaño, pero también borra los datos, por lo que no se puede usar para actualizar el tamaño después de la leer() llamar.

¿Hay una manera trivial de leer datos? directamente en vectores y actualizar el tamaño después? Sí, conozco las soluciones obvias, como usar una matriz pequeña como un búfer de lectura temporal y usar vector::insertar() para agregar eso al final del vector:

char tmp[N];
int M = read(fd, tmp, N);
buf.insert(buf.end(), tmp, tmp + M)

Esto funciona (y es lo que estoy haciendo hoy), pero me molesta que haya una operación de copia adicional que no sería necesaria si pudiera poner los datos directamente en el vector.

Entonces, ¿existe una forma sencilla de modificar el tamaño del vector cuando los datos se han agregado externamente?

  • Está seguro &buf[0] funciona en modo depuración? Por ejemplo, en Visual Studio, en modo de depuración std::vector::operator[] realiza una verificación de rango. Entonces esa expresión arrojará si buf esta vacio.

    – Pretoriano

    7 oct 2011 a las 15:41


  • Uso GCC y ejecuté el programa a través de valgrind para asegurarme de que no ocurrieron errores de memoria. Todo lo que puedo decir es que con la implementación GNU libstdc++, esto funciona. & vec[0] parece darle un puntero directo a la memoria reservada, sin importar el tamaño ().

    – usuario984228

    7 oct 2011 a las 16:11

  • @user984228: si está feliz de confiar en los detalles de implementación de GCC (que es una MALA IDEA (TM)), entonces miraría la fuente para su implementación de vector. Puede ver dónde almacena el begin y end punteros y capacidad, y si solo sobrescribe el end puntero, estoy bastante seguro de que cambiará el tamaño como desee. Simplemente copie cualquiera que sea la implementación de resize() lo hace en el caso de que la capacidad sea lo suficientemente grande para empezar, omitiendo el memset/fill/lo que sea. Tendrás que solucionar algunos private modificadores, por supuesto, tal vez mediante codificación fija en las compensaciones.

    –Steve Jessop

    7 oct 2011 a las 16:24


  • @SteveJessop: Acabo de morir un poco.

    – Matthieu M.

    7 oct 2011 a las 16:34

  • @Matthieu: bastante. Si todo eso suena como una mala idea, con suerte confiar en el hecho de que GCC parece permitirle escribir en un espacio que solo está reservado, no redimensionado, también suena como una mala idea 🙂

    –Steve Jessop

    7 oct 2011 a las 17:49

avatar de usuario
Robᵩ

vector<char> buf;
buf.reserve(N);
int M = read(fd, &buf[0], N);

Este fragmento de código invoca un comportamiento indefinido. No puedes escribir más allá de size() elementos, incluso si ha reservado el espacio.

El código correcto es como:

vector<char> buf;
buf.resize(N);
int M = read(fd, &buf[0], N);
buf.resize(M);

PD. Su afirmación “Con los vectores, se puede suponer que los elementos se almacenan de forma contigua en la memoria, lo que permite que el rango [&vec[0]&vec[vec.capacity()) to be used as a normal array” isn’t true. The allowable range is [&vec[0]&vec[vec.size()).

  • There is no way to avoid the unnecessary initialization that the first resize() causes?

    – user984228

    Oct 7, 2011 at 15:35

  • 99% certainty the extra initialization will be dwarfed by the cost of your I/O anyway.

    – Mark B

    Oct 7, 2011 at 15:39

  • @user984228: The question is whether that is a problem. If you have measured and that initialization becomes a bottleneck (I would not expect that) then you might need to consider implementing your own data structure… Note: if and only if, I am not trying to have you implement your own data type, but rather realize that in most cases that will not be a performance bottleneck –i.e. wherever you are reading from is probably much slower than the cost of that initialization.

    – David Rodríguez – dribeas

    Oct 7, 2011 at 15:40

  • @MarkB: would not you expect a good implementation of insert (range) to be specialized with a single reserve call for random iterators ?

    – Matthieu M.

    Oct 7, 2011 at 16:14

  • @user984228: Then I'd rather just use the temporary buffer + insert. It should be at least as efficient, Incorrect. The temporary buffer avoids the zero initialization, reads into buffer, then requires a copy from buffer to vector. Vector resize has a zero initialization, then reads into vectors. Zero initialization is at least as fast, probably faster, than a copy. Ergo, resize is still faster than a buffer.

    – Mooing Duck

    Oct 7, 2011 at 16:17

It looks like you can do what you want in C++11 (though I haven’t tried this myself). You’ll have to define a custom allocator for the vector, then use emplace_back().

First, define

struct do_not_initialize_tag {};

Then define your allocator with this member function:

class my_allocator {
    void construct(char* c, do_not_initialize_tag) const {
        // do nothing
    }

    // details omitted
    // ...
}

Now you can add elements to your array without initializing them:

std::vector<char, my_allocator> buf;
buf.reserve(N);
for (int i = 0; i != N; ++i)
    buf.emplace_back(do_not_initialize_tag());
int M = read(fd, buf.data(), N);
buf.resize(M);

The efficiency of this depends on the compiler’s optimizer. For instance, the loop may increment the size member variable N times.

Another, newer, question, a duplicate of this one, has an answer, which looks like exactly what is asked here. Here’s its copy (of v3) for quick reference:

It is a known issue that initialization can not be turned off even
explicitly for std::vector.

People normally implement their own pod_vector<> that does not do
any initialization of the elements.

Another way is to create a type which is layout-compatible with char,
whose constructor does nothing:

struct NoInitChar
{
    char value;
    NoInitChar() {
        // do nothing
        static_assert(sizeof *this == sizeof value, "invalid size");
        static_assert(__alignof *this == __alignof value, "invalid alignment");
    }
};

int main() {
    std::vector<NoInitChar> v;
    v.resize(10); // calls NoInitChar() which does not initialize

    // Look ma, no reinterpret_cast<>!
    char* beg = &v.front().value;
    char* end = beg + v.size();
}

Your program fragment has entered the realm of undefined behavior.

when buf.empty() is true, buf[0] tiene un comportamiento indefinido, y por lo tanto &buf[0] también es indefinido.

Este fragmento probablemente hace lo que quieres.

vector<char> buf;
buf.resize(N); // preallocate space
int M = read(fd, &buf[0], N);
buf.resize(M); // disallow access to the remainder

  • por supuesto también puedes llamar reserve de antemano con el tamaño del archivo para evitar todas las reasignaciones.

    – Matthieu M.

    7 oct 2011 a las 16:37

¿Ha sido útil esta solución?