类型转换方法指向父类方法

时间:2012-02-04 15:41:27

标签: c++

我想做的事情有点模糊,所以让我举一个例子(这不是实际的代码):

template <class T>
class ArrayStorage {
protected:
    void processStuff(void (ArrayStorage<T>::*procedure)(T *)) {
        for (int i = 0; i < count; i++)
            (this->*procedure)(content[i]);
    }
    // No method of type void (ArrayStorage<T>::*)(T *)

private:
    T **content;
    int count;
};

class DrawableStorage : public ArrayStorage<Drawable> {
public:
    void drawStuff() {
        processStuff((void (ArrayStorage<Drawable>::*)(Drawable *)) &DrawableStorage::drawOne);
    }

private:
    void drawOne(Drawable *item) {
        item->draw();
    }
};

基本上,有一个通用容器能够遍历其项目并在每个项目上使用一个方法(其指针在参数中)。但是,此方法不存在于此类中,而只存在于其子类中。您可以在子类“drawStuff”方法中看到,我提供了子类的方法指针,但是将其类型化为基类的方法。

令人惊讶的是,这已成功编译并且工作正常。

我的问题是,我的特定编译器是否能够处理它只是一个巧合,而事实上它完全无效,我应该摆脱它,或者它是否正确使用方法指针?

谢谢。

4 个答案:

答案 0 :(得分:3)

这是指针可能的技巧之一。它的工作原理是因为DrawableStorage是一个ArrayStorage,并且函数头与预期的匹配(将指针指向与模板对象类型匹配的类型并返回void)。

一旦父对象具有指向函数的指针,即使它实际上没有将该函数作为其自身的成员,它也可以调用它。

虽然一开始读起来可能有点混乱,但它完全有效地使用了函数指针。

答案 1 :(得分:1)

以下是您示例的更简单版本:

#include <iostream>
using namespace std;

struct Arg
{
    int i;
};

class A;
typedef void (A::*fncptr)(Arg&);

fncptr是指向A类的成员函数(方法)的指针,它通过引用获取Arg。如果有人给你这种类型的非静态函数的地址,只有当你有一个类A的对象时,你才能调用它(否则它必须是静态函数)。

class A
{
public:
    A(Arg a) : a(a) { }
    void process(fncptr f){ (this->*f)(a); }

protected:
    Arg a;
};

class B : public A
{
public:
    B(Arg a) : A(a) { } 
    void magic(){ process((fncptr)&B::function); }
    void function(Arg& a) { cout << a.i; }
};

这是诀窍:因为你在函数process中是类A的成员函数,所以你不需要指向类A的对象的指针来调用函数{{ 1}}因为你已经拥有一个:f。因此,您可以调用此类或派生类的任何函数,并且只需要该函数的地址即可。

这是this

main()

输出:int main() { Arg a; a.i = 71; B* b = new B(a); b->magic(); delete b; }

答案 2 :(得分:1)

根据$ 5.2.9 / 12,这是合法的:

  

类型为“指向cv1 T类型D的成员的指针”的prvalue可以是   转换为cv2 T类型的“指向B成员的指针”的prvalue,   其中B是D的基类(第10条),如果是有效标准   从“指向T类型B的成员的指针”转换为“指向的指针”   类型T的D成员“存在(4.11),并且cv2是相同的   cv-quali fi cation,或者比cv1更高的cv-quali fi cation。 69无效   成员指针值(4.11)转换为空成员指针   目的地类型的值。如果B类包含原件   member,或者是包含该类的类的基类或派生类   原始成员,指向成员的结果指针指向   原始成员。否则,演员的结果是未定的。 [注意:   虽然B类不需要包含原始成员,但动态   必须是取消引用成员指针的对象的类型   包含原始成员;见5.5。 - 后注]

但是,如果您尝试使用多重继承或虚拟继承来执行此操作,那么事情就会开始中断,因为并非所有编译器都以标准兼容的方式实现成员函数指针。在MSVC和英特尔编译器上,所有成员函数指针的大小都不相同,因此您将失去关于转换的重要信息。

答案 3 :(得分:0)

基类没有拥有所请求的成员函数,所以没有办法解决这个问题。但是,您可以将基本功能改为模板

template <typename S>
void processStuff(S * s, void (S::*procedure)(T *))
{
    for (int i = 0; i < count; i++) { (s->*procedure)(content[i]); }
}

按如下方式调用它:

processStuff(this, &DrawableStorage::drawOne);