0

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).

4

3 回答 3

1

我想知道这是否不好(TM)

当然是,除非它是对在公共基类中声明的虚函数的覆盖。

如果是,那么您不需要狡猾的演员阵容;只需直接从&Base::DoSomethingA.

如果不是,那么邪恶的 C 强制转换(这里是reinterpret_cast变相的)允许您将指针应用于没有该成员函数的类型;称弗兰肯斯坦的可憎之物绝对可以做任何事情。如果该函数实际上没有接触到对象,那么您很有可能不会看到任何不良影响;但你仍然坚定地处于未定义的行为中。

于 2013-04-17T11:54:35.823 回答
1

一般情况下是不安全的。

在这一行中,您使用回调的 C 样式强制转换破坏了类型安全系统:

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

DerivedA 的成员函数应该仅对 DerivedA 的实例(或进一步派生自它的东西)进行操作。你没有,你有一个 DerivedB,但是因为你的 C-cast,你的代码被编译了。

如果您的回调函数实际上试图访问 DerivedA 的成员,您可能会遇到严重的问题(未定义的行为)。

因为它是只打印的函数,所以在这种情况下可能不是未定义的,但这并不意味着你应该这样做。

一种类型安全的方法是使用一个回调,它接受一个 Base(引用)和一个 float 并使用boost::bindstd::bind创建它。

在大多数情况下,另一种简单的方法可能是您的答案,那就是调用 Base 的虚拟方法,该方法采用浮点数。

于 2013-04-17T11:57:42.087 回答
1

是UB。

您甚至可以使用 static_cast 代替令人讨厌的 C 风格的演员表,而且演员表本身是非常合法的。但

[注意:虽然B类不需要包含原始成员,但解除引用成员指针的对象的动态类型必须包含原始成员;见 5.5。—尾注] (5.2.9 12)

“第一个操作数称为对象表达式。如果对象表达式的动态类型不包含指针所指的成员,则行为未定义”(5.5 4)

即,当您从动态类型 DerivedB 的对象调用它时,您会变得未定义。

现在,作为一个肮脏的黑客,它可能不是最糟糕的(比手动遍历 vtables 更好),但它真的需要吗?如果您不需要任何动态数据,为什么要在 DerivedB 上调用它?Base 在库中,您无法重新定义它。Callback 也是图书管理员,所以你必须拥有这个typedef void (Base::*SEL_SCHEDULE)(float);,好的。但是为什么不能doSomething为 B 定义并指向它以与 DerivedB 的实例结合呢?你说

doSomethingA 不访问其任何成员变量或调用成员函数。它访问静态成员并调用其他静态函数

但你也可以这样做doSomethingB。或者,如果您的回调与对象类型完全分离,并且您需要成员函数指针的唯一原因是符合库回调签名,您可以使您的实际回调非成员普通旧函数,并从一个调用它们-线成员回调构造函数,如DoSomething(float dt) {ReallyDoSomething(dt);}.

于 2013-04-17T18:26:51.417 回答