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 在继承关系中的体现:在继承关系中,虚函数调用通过虚函数表实现动态绑定,基类指针可以调用派生类的虚函数。
相关推荐
Gkoob2 分钟前
Vue3+Three.js 打造实时设备状态 3D 可视化面板
开发语言·javascript·3d
m0_716765233 分钟前
C++巩固案例--通讯录管理系统详解
java·开发语言·c++·经验分享·学习·青少年编程·visual studio
G果9 分钟前
ros2工程 debug(vscode)
c++·ide·vscode·编辑器·bug·debug·ros2
jf加菲猫11 分钟前
第10章 数据处理
xml·开发语言·数据库·c++·qt·ui
酉鬼女又兒14 分钟前
零基础快速入门前端深入掌握箭头函数、Promise 与 Fetch API —— 蓝桥杯 Web 考点全解析(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·css·职场和发展·蓝桥杯·es6·js
迷藏49417 分钟前
**发散创新:Go语言中基于上下文的优雅错误处理机制设计与实战**在现代后端开发中,**错误处理**早已不是简单
java·开发语言·后端·python·golang
2301_7644413317 分钟前
基于python实现的便利店投资分析财务建模评估
开发语言·python·数学建模
杰克尼20 分钟前
知识点总结--day10(Spring-Cloud框架)
java·开发语言
wengqidaifeng22 分钟前
备战蓝桥杯----C/C++组 (三)算法讲解前言
c语言·c++·蓝桥杯
okiseethenwhat27 分钟前
Java 内部类详解
java·开发语言