简体   繁体   中英

C++: call (derived's) member function on base pointer of a different derived class's object

Is it "safe" (and/or portable) to call a member function (pointer) on the pointer of a base class, but the object pointed to is an instance different derived class. The member function does not access any member variables or functions of the derived class.

/* Shortened example of what happens in the client code and library */
class Base { /* ... */ }
class DerivedA : public Base {
    /* ... */ 
    public: void doSomethingA(float dt);
}
void DerivedA::doSomethingA(float dt) {
    /* Does not access members. Conventionally calls/accesses statics */
    cout << "dt(" << dt << ")";
}

class DerivedB : public Base { /* ... */ }

typedef void (Base::*SEL_SCHEDULE)(float);
SEL_SCHEDULE pCallback = (SEL_SCHEDULE)(&DerivedA::doSomethingA);

DerivedB db = new DerivedB();
Base *b = &db;
/* pCallback and b are saved in a list elsewhere (a scheduler) which calls */
(b->*pCallback)(0.f);

This seems to work (in MSVC/Debug mode) okay at runtime, but I'm wondering whether this is Bad (TM) - and why? (I'm yet to test this code with the compilers for Android and iOS).

Some more specifics if required: I'm building a cocos2d-x based project. Base is CCObject , DerivedA and DerivedB are subclasses of CCLayer .

The hierarchy is DerivedA and DerivedB < CCLayer < CCNode < CCObject . They're game scenes which are visible/alive at mutually exclusive times.

DerivedA has a different static function to set up playback of music which receives a CCNode caller object as a parameter and schedules another selector ( doSomethingA ) to begin playback and slowly fade it in using something like:

callerNode->schedule(schedule_selector(DerivedA::doSomethingA), 0.05f);

schedule_selector is what does the C-style cast. doSomethingA does not access any of its member variables or call member functions. It accesses static members and calls other static functions such as such as

CocosDenshion::SimpleAudioEngine::sharedEngine()->setBackgroundMusicVolume(sFadeMusicVolume);

The call to doSomethingA at runtime happens in CCTimer::update .

The hack is primarily to avoid duplicating code and conform to the library's callback signature (timer/scheduler system).

I'm wondering whether this is Bad (TM)

It certainly is, unless it's an override of a virtual function declared in a common base class.

If it is, then you don't need the dodgy cast; just initialise directly from &Base::DoSomethingA .

If it isn't, then the evil C cast (which here is a reinterpret_cast in disguise) allows you to apply the pointer to a type that doesn't have that member function; calling that Frankensteinian abomination could do absolutely anything. If the function doesn't actually touch the object, then there's a good chance that you won't see any ill effects; but you're still firmly in undefined behaviour.

It is not safe in general.

You have broken the type-safety system with a C-style cast of your callback in this line:

SEL_SCHEDULE pCallback = (SEL_SCHEDULE)(&DerivedA::doSomethingA);

A member function of DerivedA should operate on an instance of a DerivedA only (or something that further derives from it). You don't have one, you have a DerivedB, but because of your C-cast, your code compiled.

If your callback function had actually tried to access a member of a DerivedA you would potentially have had serious issues (undefined behaviour).

As it is the function only prints so in this case is probably not undefined, but that doesn't mean you should do it.

One typesafe way is to use a callback that takes a Base (reference) and a float and use boost::bind or std::bind to create it.

The other simple way which will probably be your answer most of the time is to just call a virtual method of Base that takes a float.

It's UB.

You can even use static_cast instead of the odious C-style cast, and the cast itself is quite legal. But

[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; see 5.5. —end note ] (5.2.9 12)

"The first operand is called the object expression. If the dynamic type of the object expression does not contain the member to which the pointer refers, the behavior is undefined" (5.5 4)

Ie, you go undefined when you call it from an object of dynamic type DerivedB.

Now, as a dirty hacks goes, it's probably not the worst (better than the manual traversing of vtables), but is it really needed? If you don't need any dynamic data, why call it on DerivedB? Base is in the library, you cannot redefine it. Callback is librarian, too, so you have to have this typedef void (Base::*SEL_SCHEDULE)(float); , OK. But why can't you define doSomething for B and make pointer to it to couple with an instance of DerivedB? You say

doSomethingA does not access any of its member variables or call member functions. It accesses static members and calls other static functions

But you can do it in doSomethingB as well. Or, if your callbacks are completely uncoupled from object types, and the only reason you need a member function pointer is the conformance to the library callback signature, you can make your actual callbacks non-member plain old functions, and call them from one-line members-callback conformers like DoSomething(float dt) {ReallyDoSomething(dt);} .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM