Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reserve() - data() trick on empty vector - is it correct?

Tags:

c++

c++11

stl

As we know, std::vector when initialized like std::vector vect(n) or empty_vect.resize(n) not only allocates required amount of memory but also initializes it with default value (i.e. calls default constructor). This leads to unnecessary initialization especially if I have an array of integers and I'd like to fill it with some specific values that cannot be provided via any vector constructor.

Capacity on the other hand allocates the memory in call like empty_vect.reserve(n), but in this case vector still is empty. So size() returns 0, empty() returns true, operator[] generates exceptions.

Now, please look into the code:

{ // My scope starts here...

    std::vector<int> vect;
    vect.reserve(n);
    int *data = vect.data();

    // Here I know the size 'n' and I also have data pointer so I can use it as a regular array.
    // ...

} // Here ends my scope, so vector is destroyed, memory is released.

The question is if "so I can use it as array" is a safe assumption?

No matter for arguments, I am just curious of above question. Anyway, as for arguments:

  1. It allocates memory and automatically frees it on any return from function
  2. Code does not performs unnecessary data initialization (which may affect performance in some cases)
like image 207
no one special Avatar asked Dec 23 '22 20:12

no one special


1 Answers

No, you cannot use it.

The standard (current draft, equivalent wording in C++11) says in [vector.data]:

constexpr T* data() noexcept;

constexpr const T* data() const noexcept;

Returns: A pointer such that [data(), data() + size()) is a valid range. For a non-empty vector, data() == addressof(front()).

You don't have any guarantee that you can access through the pointer beyond the vector's size. In particular, for an empty vector, the last sentence doesn't apply and so you cannot even be sure that you are getting a valid pointer to the underlying array.

There is currently no way to use std::vector with default-initialized elements.


As mentioned in the comments, you can use std::unique_ptr instead (requires #inclue<memory>):

auto data = std::unique_ptr<int[]>{new int[n]};

which will give you a std::unique_ptr<int[]> smart pointer to a dynamically sized array of int's, which will be destroyed automatically when the lifetime of data ends and that can transfer it's ownership via move operations.

It can be dereferenced and indexed directly with the usual pointer syntax, but does not allow direct pointer arithmetic. A raw pointer can be obtained from it via data.get().

It does not offer you the std::vector interface, though. In particular it does not provide access to its allocation size and cannot be copied.


Note: I made a mistake in a previous version of this answer. I used std::make_unique<int[]> without realizing that it actually also performs value-initialization (initialize to zero for ints). In C++20 there will be std::make_unique_default_init<int[]> which will default-initialize (and therefore leave ints with indeterminate value).

like image 82
walnut Avatar answered Dec 25 '22 22:12

walnut