指向成员函数的指针和 vcall

1. 不用类对象能否调用类的虚函数和普通函数

从本质上来讲:只要一个函数有地址,就可以直接去调用(类的普通成员函数包括虚函数都是有独立地址的)。

示例代码
#include <iostream>

// 将一个成员函数的地址转换成普通地址
template<typename dst_type, typename src_type>
dst_type pointer_cast(src_type src)
{
    return *reinterpret_cast<dst_type*>(&src);
}

class TDF
{
public:
    void myf()
    {
        std::cout << "The myf() member function executes" << std::endl;
    }

    void myfWithParam(int val)
    {
        std::cout << "The myfWithParam() member function executes with value: " << val << std::endl;
    }

    virtual void myVirtualFunc()
    {
        std::cout << "The myVirtualFunc() virtual function executes" << std::endl;
    }
};

int main()
{
    // 获取成员函数的地址
    auto memberFuncPtr = &TDF::myf;
    auto memberFuncWithParamPtr = &TDF::myfWithParam;
    auto virtualFuncPtr = &TDF::myVirtualFunc;

    // 打印成员函数的地址
    std::cout << "The address of the TDF::myf function is " << reinterpret_cast<void*>(memberFuncPtr) << std::endl;
    std::cout << "The address of the TDF::myfWithParam function is " << reinterpret_cast<void*>(memberFuncWithParamPtr) << std::endl;
    std::cout << "The address of the TDF::myVirtualFunc function is " << reinterpret_cast<void*>(virtualFuncPtr) << std::endl;

    // 将成员函数指针转换为普通函数指针
    typedef void(*Func)();
    typedef void(*FuncWithParam)(int);
    Func func1 = pointer_cast<Func>(memberFuncPtr);
    FuncWithParam func2 = pointer_cast<FuncWithParam>(memberFuncWithParamPtr);
    Func func3 = pointer_cast<Func>(virtualFuncPtr);

    // 直接调用普通函数指针
    func1();   // 正常调用
    func2(20); // 能调用,但传入的值读取错误 与 pointer_cast<Func>(memberFuncWithParamPtr) 不传入参数一样
    func3();   // 没有被调用,程序异常结束

    return 0;
}
解释
  1. 模板函数 pointer_cast:用于将成员函数指针转换为普通函数指针。
  2. 获取成员函数的地址:通过 &TDF::myf&TDF::myfWithParam&TDF::myVirtualFunc 获取无参数和有参数的成员函数以及虚函数的地址。
  3. 转换为普通函数指针:使用 pointer_cast 将成员函数指针转换为普通函数指针。
  4. 直接调用普通函数指针func1func2func3 调用无参数和有参数的成员函数以及虚函数。
注意事项
  • 这种方法依赖于编译器的实现细节,可能在不同的编译器或平台上表现不同。
  • 直接调用成员函数指针可能会导致未定义行为,特别是在涉及虚函数时,因为虚函数调用需要通过虚函数表(vtable)进行动态绑定。

这种方法展示了如何在不用类对象的情况下调用类的虚函数和普通成员函数,但应谨慎使用,确保代码的可移植性和安全性。

2. 指向成员函数的指针

指向成员函数的指针是C++中的一种特殊指针,用于指向类的成员函数。它的语法与普通函数指针不同,需要指定类类型。

  • 定义和使用指向成员函数的指针

    class MyClass {
    public:
        void memberFunc() {
            std::cout << "Member function called" << std::endl;
        }
    };
    
    int main() {
        MyClass obj;
        void (MyClass::*funcPtr)() = &MyClass::memberFunc;
        (obj.*funcPtr)(); // 通过对象和指针调用成员函数
        return 0;
    }
    

3. 指向虚成员函数的指针和 vcall

指向虚成员函数的指针与指向普通成员函数的指针类似,但在调用时会通过虚函数表(vtable)进行动态绑定(vcall)。

  • 定义和使用指向虚成员函数的指针

    class Base {
    public:
        virtual void virtualFunc() {
            std::cout << "Base virtual function called" << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        void virtualFunc() override {
            std::cout << "Derived virtual function called" << std::endl;
        }
    };
    
    int main() {
        Derived obj;
        void (Base::*funcPtr)() = &Base::virtualFunc;
        (obj.*funcPtr)(); // 通过对象和指针调用虚成员函数,实际调用 Derived::virtualFunc
        return 0;
    }
    

4. vcall 在继承关系中的体现

在继承关系中,虚函数调用(vcall)通过虚函数表(vtable)实现动态绑定。每个多态类都有一个虚函数表,包含指向虚函数的指针。对象通过虚指针(vptr)指向虚函数表,从而实现动态绑定。

  • 继承关系中的 vcall

    class Base {
    public:
        virtual void virtualFunc() {
            std::cout << "Base virtual function called" << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        void virtualFunc() override {
            std::cout << "Derived virtual function called" << std::endl;
        }
    };
    
    void callVirtualFunc(Base* obj) {
        obj->virtualFunc(); // 通过基类指针调用虚函数,实际调用 Derived::virtualFunc
    }
    
    int main() {
        Derived obj;
        callVirtualFunc(&obj); // 传递 Derived 对象的地址
        return 0;
    }
    

    在上述代码中,callVirtualFunc 函数通过基类指针调用虚函数 virtualFunc,实际调用的是 Derived 类的 virtualFunc,这就是 vcall 在继承关系中的体现。

总结

  • 不用类对象调用函数:直接调用成员函数指针可能会导致未定义行为,特别是在涉及虚函数时,因为虚函数调用需要通过虚函数表(vtable)进行动态绑定。
  • 指向成员函数的指针:指向成员函数的指针用于指向类的成员函数,可以通过对象和指针调用成员函数。
  • 指向虚成员函数的指针和 vcall:指向虚成员函数的指针在调用时通过虚函数表进行动态绑定,实现多态性。
  • vcall 在继承关系中的体现:在继承关系中,虚函数调用通过虚函数表实现动态绑定,基类指针可以调用派生类的虚函数。
08-13 04:55