C++ 函数语义学——指向成员函数的指针和 vcall

指向成员函数的指针和 vcall

指向成员函数的指针和 vcall

    • [指向成员函数的指针和 vcall](#指向成员函数的指针和 vcall)
      • [1. 不用类对象能否调用类的虚函数和普通函数](#1. 不用类对象能否调用类的虚函数和普通函数)
      • [2. 指向成员函数的指针](#2. 指向成员函数的指针)
      • [3. 指向虚成员函数的指针和 vcall](#3. 指向虚成员函数的指针和 vcall)
      • [4. vcall 在继承关系中的体现](#4. vcall 在继承关系中的体现)
      • 总结

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

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

示例代码
cpp 复制代码
#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++中的一种特殊指针,用于指向类的成员函数。它的语法与普通函数指针不同,需要指定类类型。

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

    cpp 复制代码
    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)。

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

    cpp 复制代码
    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

    cpp 复制代码
    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 在继承关系中的体现:在继承关系中,虚函数调用通过虚函数表实现动态绑定,基类指针可以调用派生类的虚函数。
相关推荐
Chef_Chen1 分钟前
从0开始学习R语言--Day39--Spearman 秩相关
开发语言·学习·r语言
不学会Ⅳ9 分钟前
Mac M芯片搭建jdk源码环境(jdk24)
java·开发语言·macos
2401_8812444012 分钟前
牛客周赛99
c++
好开心啊没烦恼1 小时前
Python 数据分析:计算,分组统计1,df.groupby()。听故事学知识点怎么这么容易?
开发语言·python·数据挖掘·数据分析·pandas
lljss20202 小时前
Python11中创建虚拟环境、安装 TensorFlow
开发语言·python·tensorflow
山登绝顶我为峰 3(^v^)33 小时前
如何录制带备注的演示文稿(LaTex Beamer + Pympress)
c++·线性代数·算法·计算机·密码学·音视频·latex
Python×CATIA工业智造5 小时前
Frida RPC高级应用:动态模拟执行Android so文件实战指南
开发语言·python·pycharm
十五年专注C++开发6 小时前
CMake基础:条件判断详解
c++·跨平台·cmake·自动化编译
我叫小白菜6 小时前
【Java_EE】单例模式、阻塞队列、线程池、定时器
java·开发语言
狐凄6 小时前
Python实例题:基于 Python 的简单聊天机器人
开发语言·python