why it use void** here?

662 views Asked by At

the code pick up from v8-0.2.5

/**
 * Checks whether two handles are the same.
 * Returns true if both are empty, or if the objects
 * to which they refer are identical.
 * The handles' references are not checked.
 */
template <class S> bool operator==(Handle<S> that) {
  void** a = reinterpret_cast<void**>(**this);
  void** b = reinterpret_cast<void**>(*that);
  if (a == 0) return b == 0;
  if (b == 0) return false;
  return *a == *b;
}  

Handle Overload operator* so that **this and *that return type T*.

So it seems

  void* a = reinterpret_cast<void*>(**this);
  void* b = reinterpret_cast<void*>(*that);
  return a == b;

will also work well?

3

There are 3 answers

2
sepp2k On

If a and b had type void*, then you couldn't dereference them (without casting them to something else first), so *a == *b wouldn't work.

2
Javier Martín On

First, I admid that I only read the code in the link diagonally. Apparently, the Handle class overloads the dereferencing operator (*) to return the T* that is being handled. Thus, the expressions in the first line mean the following:

  • this is a (possibly cv-qualified) Handle<T> * const.
  • *this is Handle<T> &
  • **this is the return value of the handle's operator*, which is the T* you mentioned.
  • Finally, that T* is reinterpreted into a void**. Note that one extra indirection is added, so dereferencing the result is possible and will yield a void* & instead of a T&
  • The equivalent line with that yields a S* that is reinterpreted as a void**.

Thus, you get a couple of pointers to different types T* and S* that are magically reinterpreted as void**. Then the code performs null checks and then, the magic line:

return *a == *b;

Which is comparing the (possibly unaligned!) first sizeof(void*) bytes of the objects of types T and S that are actually pointed to by a and b. Unless you can be completely sure that T and S have the proper size and alignment, the check is completely bogus. For example, the check would make sense if you know that T itself is always a pointer or smart pointer object with the same size of a pointer, so you may have different handles point to different pointer objects that nevertheless point (in the 2nd indirection level) to the same object. This allows the GC to move the underlying object without having to update all handles to it, by simply updating the contents of the 1st level pointers.

Handle<T> has T* -----> T = U* pinned in memory -----> actual object U can be moved

So, to answer your question, casting only to void* (without increasing the indirection) is not the same as making the check in the code - your version would compare the pointers, so in my example, two different handles to the same object might compare unequal with your alternate code.

PS: it is also bad style to have your class return T* from both operator* and operator->, because then you are breaking the general identity between p->x and (*p).x. The de-referencing operator should generally return a T& if the member access operator returns a T*.

0
Alex P. On

Javier Martín is right. You can't just compare pointers as you sugested in question. First of all in context ov v8 the Handle<T> has a restriction on type T. You can't take any class and apply Handle to it. T is just a facade class that the user deal with: v8::String, v8::Integer and so on. The object of such types is never created but such classes are used as interfaces to internals.

In reality the pointer that Handle<> stores is a pointer to tomething let say "Tag". We have the same object that two Handle<> refer if their tags are the same. Internally tag has size of void* and somehow refers to the real object. The user doesn't need to know what Tag is so Handle<> uses void* instead. Some thoughts

  1. Handle<T> has T* --> Tag (not T) --(somehow)--> real object (not of type T)

  2. Tag has size of void*. And user does not need to know about real type of Tag.

  3. Two tags are equal - they refer to the same object.

  4. (summary, from the user sight of view) Handle<T> has void** --> void*

So the original bool operator==(Handle<S> that) is doing what is supposed to do: compare values of tags. But check pointers at first.