Skip to main content
gamedev.net
🔒 Locked

[C++] Member Function Pointer Madness (?)

Started by Red Ant 8 replies 2.2k views
Original Post
Red Ant
Red Ant
Hello, I've inherited a super-messy C++ project from a coworker who's left the company ... let's just call him Jim (not his real name). At present I'm trying to replace any C-style casts with proper C++ casts. I've come across a piece of code that does something which I find rather weird. I'm suspecting that this may not even be valid C++, but I'm not sure so I thought I'd ask the forum. So here goes ... say you have a base class, called Base. Base has a number of methods, their precise nature is not important for this example.

class Base
{
public:
	void foo();
	void bar();
};

Then you go and define a type of pointer that points to one of those member functions, like so:

typedef void ( Base::*FuncPtr )();

Now we add another class, called Derived, which - you guessed it - is derived from Base. Derived does not reimplement any of the functions it inherits from Base (none of the functions in Base are virtual anyway), but it does define an additional method:

class Derived : public Base
{
public:
	void someMethod();
};

There is nothing weird going on in the code I've posted yet. So far, everything is just fine (IMO). Next comes the suspicious part (using C-style casts as I found them in Jim's code).

Derived d;

Base* objectPtr = &d; // <--- OK

FuncPtr ptrToFunc = ( FuncPtr ) ( &Derived::someMethod ); // <--- Does C++ allow this???

The cast above is not possible using static_cast. The fact that a reinterpret_cast is required to make this work rings some alarm bells in my head. The code does seem to work as intended, but it still leaves me with an uneasy feeling that the effects of doing something like this might at best be 'undefined'. Is my suspicion correct?
DevFred
DevFred
My gut feeling tells me that this can't be right. A pointer-to-member-function is like an offset into a vtable. But in Base, there is no entry for that offset. If it were legal, you surely wouldn't require a cast in the first place.

But this is just guessing.
phresnel
phresnel
As we are converting pointers to functions with the same signature, the code looks fine for me. And even with -Wall -Wextra -pedantic, I get not even a warning with a static_cast<>, in g++ 3.x.

Adding a conversion to a pointer-to-function of a different signature, I succeed to fail:

typedef void ( Base::*FuncPtr2 )(int);FuncPtr2 ptrToFunc2 =  static_cast<FuncPtr2>( &Derived::someMethod );typedef int ( Base::*FuncPtr3 )();FuncPtr3 ptrToFunc3 =  static_cast<FuncPtr3>( &Derived::someMethod );


Each gives me an invalid conversion.

I guess this works because those are both non-virtual, non-static, functions, and the calling convention for those is the same.

edit: forgot to mention that I actually defined Derived::someMethod().
Red Ant
Red Ant
Hmmm, I just found an identical example in Stroustrup's The C++ Programming Language, section 15.5.1 Base and Derived Classes, where he states that

Quote:

[...] by refusing the initialization, the compiler saves us from a run-time error.

CadetUmfer
CadetUmfer
Consider refactoring to use interfaces rather than member function pointers. There's always boost::bind and the like or a non-member wrapper function if that's not an option.

Also, casting function pointers is rather evil.
Anthony Umfer
SiCrane
SiCrane
Quote:
Original post by Red Ant
The cast above is not possible using static_cast. The fact that a reinterpret_cast is required to make this work rings some alarm bells in my head. The code does seem to work as intended, but it still leaves me with an uneasy feeling that the effects of doing something like this might at best be 'undefined'. Is my suspicion correct?


No, this should be allowed by a static_cast. A static_cast can be used to reverse any implicit conversion, and contravariance means that there's a conversion from void (Base::*)(void) to void (Derived::*)(void), so a static_cast can be used to perform the reverse.
Red Ant
Red Ant
Hmm, I just tried the same on Visual C++ 2005, and here it works ... although it does require a static_cast ... if you don't use a cast at all the compiler barfs.

The SUN forte compiler doesn't even accept a static_cast and insists on a reinterpret_cast.

Also, why does Stroustrup claim that the conversion is not allowed?
visitor
visitor
The standard has this note:

Quote:

If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the result of the cast is undefined.
[Note:
although class B need not contain the original member, the dynamic type of the object on which the pointer to member is dereferenced must contain the original member. ]


So you can call someMethod through that cast pointer if you actually have an instance of Derived, but shouldn't do it if you have an instance of Base (can't call validly methods that a class doesn't have).
MaulingMonkey
MaulingMonkey
Looks legal, but it's obviously playing with fire.

If objectPtr and ptrToFunc are heavily related enough (as they hopefully are with code like this, with the assumptions it makes!), it might be feasible to replace the both of them with a boost::function, which would have the added advantage of making this type safe:
boost::function< void () > f = boost::bind( &Derived::someMethod, &d );<strike>(objectPtr->*ptrToFunc)();</strike>f();
Red Ant
Red Ant
Thanks, guys, that has cleared it up for me.

As to the suggestion to use boost::function, I wish I could. Alas, our project doesn't use boost (not my decision, I'm obviously not the project manager). But we do use the ACE library (albeit a stone age version of it), so maybe I should go looking for something similar to boost::function in ACE.

P.S. It scares me that even after 8 years of C++, I'm still occasionally surprised by some aspects of the language. :\

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.