I don't think creating a file descriptor and allowing it to be duplicated on fork() or preserved through exec should be separate operations.
I think changing fork() to close file descriptors by default would abuse it's currently clean design. It's so beautiful, like a cell dividing. The only special cases which have to exist are things like PID and PPID. Of course people added locks and asynchronous IO. I don't think complicating it more would be nice.
Similarly exec() takes an existing process and environment and just changes the running program. I don't see file descriptors as part which program is running but as part of the environment that it runs in. The simplest thing is that the exec'd program sees exactly the same set of file descriptors that there were right before the system call.
So I'd argue that the defaults make sense as they are. File descriptors are real by default. I'd say the problem is O_CLOEXEC. It seems really useful, but it doesn't fit into this model.
As someone else pointed out, a system call to close all file descriptors (or maybe ones from a given range?) would be a more orthogonal way to handle it, and would probably be useful elsewhere. I've seen lots of code that loops from 0 to 255 closing everything just to be sure it wasn't leaking something.
Now if the automatic-trigger part of O_CLOEXEC is an important feature (maybe you don't trust your code to close things before calling exec), there are some solutions entirely in userspace. First, the C library could make special versions of the exec functions available which closed everything first. You could presumably use grep, macros, or other tricks to make sure you only used those versions. Second, the C library could even make provisions to track file descriptors that should be closed on exec without any help from the kernel.
In summary fork() and exec() are two well-designed parts of Unix. Making them uglier to get rid of this flag to open() would not have been an improvement.