c++arraysstdvectorpush-back

Initializing multi-dimensional std::vector without knowing dimensions in advance


Context: I have a class, E (think of it as an organism) and a struct, H (a single cell within the organism). The goal is to estimate some characterizing parameters of E. H has some properties that are stored in multi-dimensional matrices. But, the dimensions depend on the parameters of E.

E reads a set of parameters from an input file, declares some objects of type H, solves each of their problems and fills the matrices, computes a likelihood function, exports it, and moves on to next set of parameters.

What I used to do: I used to declare pointers to pointers to pointers in H's header, and postpone memory allocation to H's constructor. This way, E could pass parameters to constructor, and memory allocation could be done afterwards. I de-allocated memory in the destructor.

Problem: Yesterday, I realized this is bad practice! So, I decided to try vectors. I have read several tutorials. At the moment, the only thing that I can think of is using push_back() as used in the question here. But, I have a feeling that this might not be the best practice (as mentioned by many, e.g., here, under method 3).

There are tens of questions that are tangent to this, but none answers this question directly: What is the best practice if dimensions are not known in advance?

Any suggestion helps: Do I have any other solution? Should I stick to arrays?


Solution

  • Using push_back() should be fine, as long as the vector has reserved the appropriate capacity.

    If your only hesitancy to using push_back() is the copy overhead when a reallocation is performed, there is a straightforward way to resolve that issue. You use the reserve() method to inform the vector how many elements the vector will eventually have. So long as reserve() is called before the vector is used, there will just be a single allocation for the needed amount. Then, push_back() will not incur any reallocations as the vector is being filled.

    From the example in your cited source:

    std::vector<std::vector<int>> matrix;
    matrix.reserve(M);
    for (int i = 0; i < M; i++)
    {
        // construct a vector of ints with the given default value
        std::vector<int> v;
        v.reserve(N);
        for (int j = 0; j < N; j++) {
            v.push_back(default_value);
        }
    
        // push back above one-dimensional vector
        matrix.push_back(v);
    }
    

    This particular example is contrived. As @kei2e noted in a comment, the inner v variable could be initialized once on the outside of the loop, and then reused for each row.

    However, as noted by @Jarod42 in a comment, the whole thing can actually be accomplished with the appropriate construction of matrix:

    std::vector<std::vector<int>> matrix(M, std::vector<int>(N, default_value));
    

    If this initialization task was populating matrix with values from some external source, then the other suggestion by @Jarod42 could be used, to move the element into place to avoid a copy.

    std::vector<std::vector<int>> matrix;
    matrix.reserve(M);
    for (int i = 0; i < M; i++)
    {
        std::vector<int> v;
        v.reserve(N);
        for (int j = 0; j < N; j++) {
            v.push_back(source_of_value());
        }
    
        matrix.push_back(std::move(v));
    }