LeetCode 200: Number of Islands
Offload more functionality into depth_first_search()
One improvement your solution has over LeetCode's is that you move the grid boundary check into depth_first_search(). However, you can do more, by having it return whether you are starting on an island or in the ocean:
bool depth_first_search(...) {
    // Checks for grid boundaries and '0' cells
    if (...)
        return false;

    // We are on an island, recurse
    ...

    return true;
}

Then, the loop inside numIslands() can be simplified:
for (std::size_t row = 0; row &lt; row_length; row++)
    for (std::size_t col = 0; col &lt; col_length; col++)
        islands += depth_first_search(grid, row, col, row_length, col_length);

Naming things
While depth_first_search() does iterate in a depth-first fashion, it is not search for anything. Rather, it is implementing a flood fill algorithm. But here we use it to remove an island. If it also performs a check to see if there is an island at the given position, I would name it check_and_remove_island(). As Emily L. mentions, a function name that reads like foo_and_bar() is usually a hint that it should be split into a foo() and bar(). If you want to go that way, I suggest you make it so you can write the following code:
if (is_island(grid, row, col)) {
    islands++;
    remove_island(grid, row, col);
}

Get rid of the size variables
I don't think it's that useful to store the sizes into separate variables, and to pass them explicitly to depth_first_search(). This information can always easily be gotten from the variable grid. This also gets rid of the check for an empty grid:
std::size_t numIslands(std::vector&lt;std::vector&lt;char&gt;&gt;&amp; grid) {
    size_t islands{};

    for(std::size_t row = 0; row &lt; grid.size(); row++)
        for (std::size_t col = 0; col &lt; grid[row].size(); col++)
            islands += check_and_remove_island(grid, row, col);

    return islands;
}

You have to modify depth_first_search() to match of course.