Not really. Since CLR's JIT outputs assembly code it has knowledge about what's in the registers or about points where it is safe to stop the code for GC because there's no pointer in any register. Since you're generating C++ code you don't have this luxury, you're at the mercy of the compiler.
Actually the CLR doesn't make any assumptions like that. During a garbage collect on a particular thread, it suspends the thread (either by suspending it from another thread or during a call into the runtime, e.g. during a new object allocation). The registers are stored onto the stack and the stack is then inspected for any value that lies within the bounds of the current heap. Any value that falls within an object is marked as live for this round. The root-set computed is therefore a super-set of the "true" root-set, since an integer which happens to hold a value that would also be a valid pointer into the heap might cause a value to live longer than it strictly should, but this doesn't matter since this is much faster than using type information to derive the true root set.
At this point the thread can be resumed.
Added to the root set (which is rounded to the nearest object and now has type information which is stored with the object) is all of the objects from the other thread's root sets, all of the static variables (using type information) and thread local variables.
1) Mark all objects as "black"
2) Mark all objects from the current live set (computed above) as "gray"
3) While there are still objects in the gray set:
4) Add all objects reachable from that object which are in the black set to the gray set.
5) Add the object to the white set.
6) Goto 3 until the gray set is empty.
7) Now all objects reachable from the live set are colored white, and all objects colored black are not reachable from the live set.