std::vector<std::vector<int>>: Debug assertion failed. C++ vector subscript out of range reserving memory

Viewed 355

What's wrong here?

std::vector<std::vector<int>> mSectionsSubsets;

int count = (int)powf(2, NUM_SECTIONS);

mSectionsSubsets.reserve(count);
for (int i = 0; i < count; i++) {
    mSectionsSubsets[i].reserve(NUM_SECTIONS);
}

On MSVC++ it says vector subscript out of range once I mSectionsSubsets[i].reserve(NUM_SECTIONS); at the first i = 0.

Now sure what's wrong and how to fix it.

2 Answers

You wrote mSectionsSubsets[i] with i from 0 to count.

Every single one of those accesses is illegal, because mSectionsSubsets has no elements in it.

Reserving capacity, and resizing the vector, are two different things.

In this particular case, perhaps:

mSectionsSubsets.resize(count);
for (int i = 0; i < count; i++) {
    mSectionsSubsets[i].reserve(NUM_SECTIONS);
}

However, overall I would caution against vectors of vectors: they're poison for your cache and just generally not necessary when your dimensions are square.

How about a nice std::vector<int> instead? If you need count*NUM_SECTIONS elements, then simply do that. You can always create a two-dimensional façade for its indexes:

i = x + width*y

Cache of a quick mockup from the comments:

#include <iostream>
#include <vector>
#include <stdexcept>
#include <cassert>

// Like a vector<vector<T>>, but *better*!
template <typename T>
class RowList
{
public:
    RowList(const std::size_t rowCount, const std::size_t maxRowLength)
        : rowCount(rowCount)
        , maxRowLength(maxRowLength)
        , data(rowCount * maxRowLength)
        , utilisation(rowCount)
    {}

    std::size_t getRowCount() const
    {
        return rowCount;
    }

    std::size_t getMaxRowLength() const
    {
        return maxRowLength;
    }

    // UB if you give an invalid row number
    std::size_t getRowLength(const std::size_t rowNumber) const
    {
        assert(rowNumber < rowCount);
        return utilisation[rowNumber];
    }

    // UB if you give an invalid row number
    void clearRow(const std::size_t rowNumber)
    {
        assert(rowNumber < rowCount);
        utilisation[rowNumber] = 0;

        #ifdef NDEBUG
            // Debug builds only - make all the dead values -1
            // so we can maybe more easily spot misuse
            const std::size_t start = rowNumber*maxRowLength;
            const std::size_t end   = start + maxRowLength;

            for (std::size_t i = start; i < end; ++i)
                data[i] = -1;
        #endif
    }

    // UB if you give an invalid row number
    // throws std::out_of_range if the row is full
    void pushToRow(const std::size_t rowNumber, T value)
    {
        assert(rowNumber < rowCount);

        std::size_t& columnNumber = utilisation[rowNumber];
        if (columnNumber == maxRowLength)
            throw std::out_of_range("Row is full!");

        data[rowNumber*maxRowLength + columnNumber] = std::move(value);
        columnNumber++;
    }

    // UB if you give an invalid row or column number
    T& elementAt(const std::size_t rowNumber, const std::size_t columnNumber)
    {
        assert(rowNumber < rowCount);
        assert(columnNumber < utilisation[rowNumber]);

        return data[rowNumber*maxRowLength + columnNumber];
    }

    // UB if you give an invalid row or column number
    const T& elementAt(const std::size_t rowNumber, const std::size_t columnNumber) const
    {
        assert(rowNumber < rowCount);
        assert(columnNumber < utilisation[rowNumber]);

        return data[rowNumber*maxRowLength + columnNumber];
    }

private:
    const std::size_t rowCount;
    const std::size_t maxRowLength;

    std::vector<T> data;
    std::vector<std::size_t> utilisation;
};

template <typename T>
std::ostream& operator<<(std::ostream& os, const RowList<T>& matrix)
{
    const auto height = matrix.getRowCount();
    for (std::size_t y = 0; y < height; ++y)
    {
        const auto width = matrix.getRowLength(y);
        const auto remainder = matrix.getMaxRowLength() - width;

        for (std::size_t x = 0; x < width; ++x)
            os << matrix.elementAt(y, x) << '\t';

        for (std::size_t i = 0; i < remainder; ++i)
            os << "?\t";

        os << '\n';
    }

    return os;
}

int main()
{
    RowList<int> matrix(5, 5);

    matrix.pushToRow(2, 100);
    matrix.pushToRow(2, 101);
    matrix.pushToRow(4, 102);
    std::cerr << matrix << '\n';

    matrix.clearRow(2);
    matrix.pushToRow(1, 103);
    std::cerr << matrix << '\n';
}

 

g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
?   ?   ?   ?   ?   
?   ?   ?   ?   ?   
100 101 ?   ?   ?   
?   ?   ?   ?   ?   
102 ?   ?   ?   ?   

?   ?   ?   ?   ?   
103 ?   ?   ?   ?   
?   ?   ?   ?   ?   
?   ?   ?   ?   ?   
102 ?   ?   ?   ?   

reserve doesn't set the size, it sets the capacity (i.e. it's more concerned with memory management). In your case mSectionsSubsets still has a zero size after mSectionsSubsets.reserve(count);.

Use resize to set the size of a vector.

Note that using .at rather than [] is safer insofar that in the former case, a runtime exception will be thrown, rather than the program behaviour simply being undefined.

Related