Some things to note :

 - A you said there are `std::min(m,n)/2;` rings. 
 - the ring `R(r, p)` has `2*(r+p-2)` elements.
 - the top left of the ring has always coordinates `(x,x)`. This is visible in your code :) `j = ring_i; v_ring.push_back(mat[ring_i][j]);`
 - Your code is using a relatively efficient amount of memory for the in-place transformation, and running in O(m*n) time, which is the best possible.
 - A performance improvement will be to use a single vector for the data in the matrix such that `m(x,y) = m.data[x * cols + y]` 


**Can we do it differently ?**

Given the local coordinates system where (0, 0) is the top left of the ring

[![qt example of this coordinate][1]][1]

A coordinate `(i,j), 0<=i<w, 0<=j<h, j == 0 || i == 0` on a side of a ring, can be moved by one position using the function

    std::pair<int, int> 
    rotated_local_next(int i, int j, int h, int w)
    {
       if(j == 0) //top , special case top-right
       {
            return i == w-1 ? {i, ++j} : {++i, j};  
       }
       if(i == w-1) //right, special case bottom-right
       {
            return j == h-1 ? {--i, j} : {i, ++j};  
       }
       if(j == h-1) //bottom, special case bottom-left
       {
            return i == 0 ? {i, --j} : {--i, j};  
       }
       if(i == 0) //left, special case top-left
       {
            return j == 0 ? {++i, j} : {i, --j};  
       }
       // unreachable       
    }

You can now implement in matrix coordinates

    std::pair<int, int> 
    rotated_matrix_next(int x, int y, int ring_i, int n, int m)
    {
         // remember ring_i is now 
         auto local_point = rotated_local_next(x-ring_i, y-ring_i, n-2*(ring_i+1), m-2*(ring_i+1))
         return {local_point.first + ring_i, local_point.second + ring_i};
    }


All you need to do now is to implement iterators satisfying [MoveAssignable and MoveConstructible][2]. The following is just pseudocode

        class ring_iterator
        {
            public:
                ptr_matrix p;
                int x, int y, int ring;
                
                // Forward
                ring_iterator operator++() { 
                     posnext = rotated_matrix_next(x, y, ring, p->size(), (*p)[0].size());
                     ring_iterator next_iter = *this;
                     next_iter.x = posnext.first;
                     next_iter.y = posnext.second;
                }
                
                
                // Swappable
                void swap(ring_iterator& other) {
                     int& mine = (*p)[x][y];
                     int& theirs = other.(*p)[x][y];
                     int tmp = mine;
                     mine = theirs;
                     theirs = tmp;
                }
                // usually required
                reference operator*() { return (*p)[x][y]; }
                pointer operator->() { &((*p)[x][y]); }
        };

Once begin(), end()  is implemented your code will become

    for(auto ring_i=0; ring_i<n_rings; ++ring_i){
         
         RingWiew view(&m, m.size(), m[0].size(), ring_i);
         int r_modulo = r % view.ringNumberOfElements();
         
        std::rotate(view.begin(),std::advance(view.begin(), r_modulo),v_ring.end());
    }

    


  [1]: https://i.sstatic.net/L3AqA.png
  [2]: http://en.cppreference.com/w/cpp/algorithm/rotate