Think of a bullet and an enemy, or the player and the floor. If these objects interact, what holds the interaction code?
Your game objects do not know about each other, nor do they perform checks against other objects. You create a collision detection and collision resolution pattern that checks your game objects and performs the appropriate actions to simulate your game physics.
The Good Stuff
From previous attempts at writing collision detection and reading this book, there are two stages for collision detection and collision resolution. The first stage (collision detection) is an early-out pass where you determine if two objects may have a potential collision. Should any two objects form a potential collision, you then pass these objects into the second stage (collision resolution) to run a more fine grained check against the objects and attempt to resolve the collision.
Somewhere in your engine/game, you will hold an array of all the objects in your world. On each frame you would loop though the array and check each object against every other object with a simple bounding box/sphere collision detection.
This kind of loop is rather inefficient but does leave room for improvement via use of spatial partitioning as an early-out for objects that are guaranteed to be too far apart to collide.
After checking the two objects for a potential collision (i.e. both objects are close enough to collide), the objects are passed off to perform a more precise collision detection routine.
Imagine you have two polygons of random shapes and sizes that are close enough to potentially intersect but do not due to their geometry:
Using bounding spheres, these two objects would create a false positive for a potential collision. This is where you would then perform a more thorough pass to determine whether the two objects do actually intersect.
Once you have found a true collision, your collision resolution step would then perform the appropriate action to resolve the objects by applying forces or moments depending on the granularity and needs of your game physics.
With this in mind, you can abstract the entire process of collision detection and resolution so that your objects need not know anything about each other, nor the process required to determine and resolve collisions. The two classes/managers that handle this for you only need to know the basic properties of each object to perform a quick and dirty check for collisions and then a more thorough check should it be required.
One way Unreal Engine 3 handles it:
The bullet gets a collision message saying it hit something, with an argument telling it what it hit. It can then call objectHit.takeDamage(self). The target then gets the TakeDamage message, with a pointer to the thing that hit it, and takes the appropriate action.
I personally like this approach because it means the bullet can take special actions (like making some sort of explosion effect depending on the type of thing hit) and the target can take special actions depending on the type of bullet.
It's also possible that the bullet knows what it does to targets, and can call function on it, like objectHit.freeze(self). Then the target knows it got hit by something that freezes it, and what kind of object that was.
EDIT: This answer is meant as a general picture of how it can work, since you're probably not working with UE3. :)
Thief did this very well in the Dark Engine with Sources and Receptrons. An object could have both of these properties, with different types. For example, a Water arrow would have a Source for WaterStim on contact. An explosion would have an AoE FireStim.
When a Water Arrow hits an object, the target object then searches its Receptrons for anything looking for a WaterStim with the appropriate intensity values. Then it executes whatever command is associated with it (in this case, turning a burning torch to an off torch, and emiting a puff of smoke.)
Since the same engine is used in SystemShock2 this is how all the different damage types are handled, different bullets have different Stims set, and different monsters then have Receptrons for the different Stim types and do damage equal to 1*, 2*, 1/2 the intensity depending on if the ammo type is "super effective" or not.
It seemed like a very flexible system as you could add sources and receptrons to objects in the level editor, (to make a one-off door which opens if hit by fire, say.) While you could also tell a receptron to "send script message" if the object had special scripts associated with it.
What you don't want to do is have to hardcode an nXn interaction matrix of all possible objects colliding with all possible objects! BY generalising interactions through standardised messages, you simplify the process.
One solution is to keep the containers of bullets and players in separate classes, and then have main() function which keeps the frame loop responsible for interaction.