tags:

views:

103

answers:

2

See this GTK callback function:

static gboolean callback(GtkWidget *widget, GdkEventButton *event, gpointer *data)
{
    AnyClass *obj = (AnyClass*) data;
    // using obj works
}

(please note the gpointer* on the data). And then the signal is connected using:

AnyClass *obj2 = new AnyClass();
gtk_signal_connect(/*GTK params (...)*/, callback, obj2);

See that the *AnyClass is going to be cast to gpointer* (void**). In fact, this is working now. The callback prototype in GTK documentation is "gpointer data" and not "gpointer *data" as shown in code, what I want to know is: how this can work ? Is this safe ?

+1  A: 

Pointer is, essentially, a number. Pointer to pointer is a number too. When you are using casting, you are basically throwing away any type information/semantics, as long as variable sizes are compatible (and every pointer, including pointers-to-pointers are the same size).

Suppose your new AnyClass object gets allocated at 0x12345678.

AnyClass *obj2 = new AnyClass(); // obj2 = 0x12345678

Then you are passing it's address to gtk_signal_connect (nb. you should use g_signal_connect):

gtk_signal_connect (/* args */, 0x12345678);

Then, your callback gets called, with argument data as you provided:

callback (address_of_widget, address_of_event_structore, 0x12345678); // because you passed 0x12345678 in gtk_signal_connect

Now, because 0x12345678 is a valid address, you can cast it to valid AnyClass pointer. data argument may be defined as gpointer************, it does not matter, because, when you cast it to AnyClass*, you throw away original type information anyway.

el.pescado
Humm, thank you for the very clear answer. Is this guaranteed to work ?
Tarantula
@Tarantula - not in C++. The implementation is free to use different sizes of pointers to point at different types. The void* pointer is guaranteed to be large enough to hold any other pointer but the same can't be said of void**.
Noah Roberts
Thank you Noah !
Tarantula
+2  A: 

It's not exactly safe, especially since in using c-style casts you've gone from doing a static_cast from T1* to void*, to a reinterpret_cast from T1* to void**. However, it works because the standard guarantees that you can reinterpret_cast from T1* to T2* (in this case T2=void*) and back again to get the same T1* you started with....IFF alignment requirements of T1* and T2* are the same.

In other words, it will work on most implementations. The cast to/from void* is guaranteed but I don't know that there are any requirements for a void**.

Doing a reinterpret_cast is always kind of risky. You have to get the T1 exactly right on both sides and the compiler has no way of helping you if you get it wrong. For example, if you cast from class T5, which is a subclass of T1, and then on the other side cast to a T1*...you could be totally screwed. The static_cast would have accounted for this I believe but a reinterpret_cast will not. It could work just fine until someone uses multiple inheritance on T5 for whatever reason.

You're not gaining anything by adding the * to the hiding type. You should consider not doing it this way.

NOTE: my answer is regarding the C++ language. You tagged your question as both so you're bound to get answers for both and they're going to be very different.

Noah Roberts