Custom matrix object in C++
That's some nicely presented code.  I found it very easy to read and understand.
A vector of rows isn't the best structure for a matrix.  The reason is that each vector has its storage elsewhere, so you lose locality of access.  A better structure is a flat array (or vector) of elements, and a knowledge of the stride from one row to the next.  (we can make the stride be the same as the row length, for simplicity; separate members for width and stride can be useful in more advanced scenarios).
    std::vector&lt;int&gt; m;
    std::size_t width;
    
public:
    # Constraint: x * y must not overflow size_t
    matrix(std::size_t x, std::size_t y)
        : m(x*y, 0),
          width{x}
    {
    }

I've made the dimensions be size_t, as that's the natural type for a size or count in C++.
Now, when we need to index into the array, we need to multiply the y value by width and add x:
int get_value(std::size_t x, std::size_t y)
{
    return m[x + y*width];
}

We can improve on this, by returning a reference to the value.  Instead of having a &quot;get&quot; and &quot;set&quot; method, we have a single method (for now), and we can give it a more convenient name:
int&amp; operator()(std::size_t x, std::size_t y)
{
    return m[x + y*width];
}

This means that instead of having to write
mm.assign_value(0,0,1); 

we can instead use the more intuitive
mm(0,0) = 1;

Now it's time admit to a slight lie above.  We actually need two methods, because if we have a const matrix, we should be allowed to read, but not write, its elements.  So we also need:
const int&amp; operator()(std::size_t x, std::size_t y) const
{
    return m[x + y*width];
}


For printing, it's helpful to provide an operator&lt;&lt;().  Mine would look like this:
friend auto&amp; operator&lt;&lt;(std::ostream&amp; os, const matrix&amp; m)
{
    for (std::size_t row = 0;  row &lt; m.height;  ++row) {
        for (std::size_t col = 0;  col &lt; m.width;  ++col) {
            os &lt;&lt; m.m[col + row*m.width] &lt;&lt; ' ';
        }
        os &lt;&lt; '\n';
    }
    return os;
}

I added a height member to make this easier.

With these changes, see how much easier it is to use:
#include &lt;iostream&gt;

int main()
{
    // create a matrix object
    matrix mm(4,6);

    // print elements
    std::cout &lt;&lt; mm;

    // grab a value and print it to console
    std::cout &lt;&lt; mm(0,0) &lt;&lt; std::endl;

    // assign a new value (v = 1) to element (0,0)
    mm(0,0) = 1;

    // re-print the updated matrix
    std::cout &lt;&lt; mm;
}


Here's the full version of matrix after my edits:
#include &lt;ostream&gt;
#include &lt;vector&gt;

class matrix {
    std::vector&lt;int&gt; m;
    std::size_t width;
    std::size_t height;

public:
    matrix(std::size_t x, std::size_t y)
        : m(x*y, 0),
          width{x},
          height{y}
    {
    }

    int&amp; operator()(std::size_t x, std::size_t y)
    {
        return m[x + y*width];
    }

    const int&amp; operator()(std::size_t x, std::size_t y) const
    {
        return m[x + y*width];
    }

    friend auto&amp; operator&lt;&lt;(std::ostream&amp; os, const matrix&amp; m)
    {
        for (std::size_t row = 0;  row &lt; m.height;  ++row) {
            for (std::size_t col = 0;  col &lt; m.width;  ++col) {
                os &lt;&lt; m.m[col + row*m.width] &lt;&lt; ' ';
            }
            os &lt;&lt; '\n';
        }
        return os;
    }
};


Further exercises

If you actually want a public get_row() (and get_column()), these will need new implementations, perhaps copying values.
Think about providing a get_subarray(x, y, width, height) to give a view of part of the matrix - you'll need new members for offset and stride.  See how we can now more easily implement get_row() and get_column() using this new method.
Make the matrix a template, so we can have elements of whatever type we choose, rather than only int.