I suspect that the main difference is that with reopen, the new stream would apply not only to subsequent uses of the $std... variable, but also to variables that previously were assigned the value of the $std... variable. This can be good or bad, depending on your situation.
This irb session shows that with reopen, a variable that was assigned previous to the stream change will acquire the newly changed stream. Note that the fileno does not change for either variable, and both variables produce no output:
> $stderr.fileno
=> 2
> stderr_copy = $stderr
=> #<IO:<STDERR>>
> stderr_copy.fileno
=> 2
> $stderr.reopen(File.open('/dev/null', 'w'))
=> #<File:/dev/null>
> stderr_copy.fileno
=> 2
> $stderr.fileno
=> 2
> $stderr.puts 'foo'
=> nil
> stderr_copy.puts 'foo'
=> nil
In contrast, when instead of using reopen, the newly opened /dev/null File object is assigned to $stderr, stderr_copy will retain its original output stream. Only $stderr gets the new fileno, and stderr_copy still produces output:
> $stderr.fileno
=> 2
> stderr_copy = $stderr
=> #<IO:<STDERR>>
> stderr_copy.fileno
=> 2
> $stderr = File.open('/dev/null', 'w')
=> #<File:/dev/null>
> $stderr.fileno
=> 10
> stderr_copy.fileno
=> 2
> $stderr.puts 'foo'
=> nil
> stderr_copy.puts 'foo'
foo
=> nil
If you want to use reopen, but want to save a copy of the original output stream, you can use dup:
> stderr_dup = $stderr.dup
=> #<IO:<STDERR>>
> stderr_dup.fileno
=> 10
> $stderr.reopen(File.open('/dev/null', 'w'))
=> #<File:/dev/null>
> $stderr.fileno
=> 2
> stderr_dup.puts 'foo'
foo
=> nil
> $stderr.puts 'foo'
=> nil