关于C++中的非静态类成员函数指针
昨天发现了一个问题,就是使用对类中的非静态成员函数使用std::bind时,不能像普通函数一样直接传递函数名,而是必须显式地调用&(取地址),于是引申出我们今天的问题:非静态类成员函数指针和普通函数指针有什么区别?
一.C++中对函数到指针的隐式转换
以前在C语言程序设计课上,老师都会说:“函数名就是指向这个函数的指针”。实际上通过查阅cppreference中的隐式转换规则,其中有这么一句关键的话道出了玄机:
函数类型
T的左值能隐式转换成指向该函数的指针纯右值。这不作用于非静态成员函数,因为不存在指代非静态成员函数的左值。
这一句话表明,实际上我们在把普通函数的函数名当作右值使用的过程中,C++隐式地将其转换成了指向该函数的的指针。这也就是我们在使用std::bind的过程中可以直接传递普通函数的函数名的原因,例如:
int foo(int a,int b)
{
return (a+b);
} int main()
{
auto function = std::bind(foo,std::placeholders::_1,std::placeholders::_2);
std::cout << function(,) << std::endl; //
return ;
}
但是,这一条隐式转换规则不作用于非静态成员函数,因为不存在指代非静态成员函数的左值。因此我们能看出,指向非静态成员函数的指针和指向普通函数的指针是有区别的。接下来我们将会讨论类成员函数指针(除非有特别说明,下文中的“类成员”都指代“非静态类成员”)。
二.类成员函数指针的声明和调用
类成员函数指针的声明有些类似普通函数指针。但是区别在于,类成员函数是类成员的一部分,所以需要将类成员函数指针使用 ::* 声明成 成员指针类型。这也表示了类成员函数指针不能单独地被调用,需要 该类型的实例使用.*(对象的成员指针) 或 指向该类型的指针使用->*(指针的成员指针) 进行调用。同时由于上文说到的C++隐式转换的规则,我们需要显示地调用 &(取地址) 获取非静态成员函数指针:
class A
{
public:
void FunA()
{
std::cout << "Function A" << endl;
}
}; int main()
{
void(A::*p1)() = &A::FunA;
//p1(); Error
A a;
A *p2 = &a;
(a.*p1)(); // Succ
(p2->*p1)(); // Succ
return ;
}
三.探究类成员函数指针
我们都知道,普通函数指针实际上指向的是函数代码的起始地址。然而类成员函数指针不仅仅是类成员函数的内存起始地址,它还解决C++中涉及到的多重继承、虚函数等导致的地址偏移问题。因此,普通函数指针的内存大小是普通指针的大小(32位系统4字节,64位系统8字节),但是成员函数指针却不一定。
1.多继承中的使用
现在有三个类A B C,其中类C public继承 了A B两个类。它们的声明和定义如下:
class A
{
public:
int a;
void funA()
{
std::cout << "function A" << std::endl;
}
}; class B
{
public:
int b;
void funB()
{
std::cout << "function B" << std::endl;
}
}; class C : public A, public B
{
public:
int c;
void funC()
{
std::cout << "function C" << std::endl;
}
};
现在我们声明一个函数指针:
int main()
{
void (C::*mfpC)() = &B::funB;
return ;
}
这个指针很特别,它是一个指向C的类成员的指针,但实际上它指向了C的基类B的成员函数B::funB。现在我们编译代码并调用gdb调试查看mfpC的信息:
(gdb) p mfpC
$1 = (void (C::*)(C * const)) 0x8000966 <B::funB()>, this adjustment 4
(gdb) p sizeof(mfpC)
$2 = 16
(gdb) x/16xb &mfpC
0x7ffffffee830: 0x66 0x09 0x00 0x08 0x00 0x00 0x00 0x00
0x7ffffffee838: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
可以看见,mfpC实际占用了16个字节(运行环境为64位操作系统),这16个字节中不仅包含了它所指向的函数的起始地址,还包含了this指针的调整值。因为对于多重继承来说,如果类成员函数指针保存的是非最左基类的成员函数地址,根据C++标准,非最左基类实例的开始地址肯定不同于派生类实例的开始地址,所以需要调整this指针,使其指向非最左基类实例。
2.有虚函数的使用
现在修改代码,在类B中定义一个虚函数funC,并在类C中重写它:
class A
{
public:
int a;
void funA()
{
std::cout << "function A" << std::endl;
}
}; class B
{
public:
int b;
void funB()
{
std::cout << "function B" << std::endl;
} virtual void funC()
{
std::cout << "function C in class B" << std::endl;
}
}; class C : public A, public B
{
public:
int c;
void funC()
{
std::cout << "function C in class C" << std::endl;
}
};
然后我们实例化一个类C的对象c和一个指向B::funC的成员函数指针mfpB,最后通过c调用它:
int main()
{
C c;
void (B::*mfpB)() = &B::funC;
(c.*mfpB)();
return 0;
}
结果显而易见,输出的不是"function C in class B",而是"function C in class C"。
由此可见,成员函数指针虽然看起来和普通的函数指针有些类似,但实际上两者还是有很大的差别的。
关于C++中的非静态类成员函数指针的更多相关文章
- C++中的Thunk技术 / 非静态类成员函数作为回调函数 的实现方法
原文:https://blog.twofei.com/616/ 用我的理解通俗地解释一下什么是C++中的Thunk技术吧! Thunk技术就是申请一段可执行的内存, 并通过手动构造CPU指令的形式来生 ...
- 为什么const对象只能调用const成员函数,而不能调用非const成员函数?
在c++中,我们可以用const来定义一个const对象,const对象是不可以调用类中的非const成员函数,这是为什么呢?下面是我总结的一些原理. 假设有一个类,名字为test代码如下: clas ...
- 成员函数指针与高性能C++委托
1 引子 标准C++中没有真正的面向对象的函数指针.这一点对C++来说是不幸的,因为面向对象的指针(也叫做“闭包(closure)”或“委托(delegate)”)在一些语言中已经证明了它宝贵的价值. ...
- [转]成员函数指针与高性能的C++委托
原文(作者:Don Clugston):Member Function Pointers and the Fastest Possible C++ Delegates 译文(作者:周翔): 成员函数指 ...
- C++ 指向类成员函数指针的用法(转自维基百科)
类成员函数指针 类成员函数指针(member function pointer),是C++语言的一类指针数据类型,用于存储一个指定类具有给定的形参列表与返回值类型的成员函数的访问信息. 目录 1 语法 ...
- 函数指针和成员函数指针有什么不同,反汇编带看清成员函数指针的本尊(gcc@x64平台)
函数指针是什么,可能会答指向函数的指针. 成员函数指针是什么,答指向成员函数的指针. 成员函数指针和函数指针有什么不同? 虚函数指针和非虚成员函数指针有什么不同? 你真正了解成员函数指针了吗? 本篇带 ...
- 从汇编看c++成员函数指针(二)
下面先看一段c++源码: #include <cstdio> using namespace std; class X { public: virtual int get1() { ; } ...
- 为什么 C++ 中成员函数指针是 16 字节?
当我们讨论指针时,通常假设它是一种可以用 void * 指针来表示的东西,在 x86_64 平台下是 8 个字节大小.例如,下面是来自 维基百科中关于 x86_64 的文章 的摘录: Pushes a ...
- 从汇编看c++中成员函数指针(一)
下面先来看c++的源码: #include <cstdio> using namespace std; class X { public: int get1() { ; } virtual ...
随机推荐
- getString()方法与getObject()方法的区别
JDBC提供了getString().getInt()和getData()等方法从ResultSet中获取数据,当查询结果集中的数据量较小时,不用考虑性能,使用这些方法完全可以满足需求,但是当Resu ...
- Java EE会话技术Cookie和Session
会话技术 一.定义 会话技术是帮助服务器记住客户端状态的(区分客户端的).将客户访问的信息存在本地的叫Cookie技术,存在服务器上的叫Session技术. 注意: 一次会话何时开始?从打开一个浏览器 ...
- mobx学习笔记02——mobx基础语法(class)
新的语法可能不被浏览器支持,可以使用babel转换为浏览器支持的代码格式: 为什么要定义class? js是一门面向对象的编程语言.需要利用类来复用代码,提高编程效率. 需要什么样的class能力? ...
- 英语单词SYNOPSIS
SYNOPSIS 来源——man帮助内容 BASH() General Commands Manual BASH() NAME bash - GNU Bourne-Again SHell SYNOPS ...
- CentOS6.5下安装jdk配置环境变量错误问题:
CentOS6.5下安装jdk,使用gedit /etc/profile 配置环境变量为如下: export JAVA_HOME=/usr/soft/jdk7export PATH=$JAVA_HO ...
- HDU 4812 (点分治)
题目:https://vjudge.net/contest/307753#problem/E 题意:给你一颗树,树上每个点都有个权值,现在问你是否存在 一条路径的乘积 mod 1e6+3 等于 k的 ...
- PHP远程DoS漏洞深入分析及防护方案
PHP远程DoS漏洞 4月3日,有人在PHP官网提交PHP 远程DoS漏洞(PHP Multipart/form-data remote dos Vulnerability),代号69364.由于该漏 ...
- window 2008/2012计划任务配置
很多人在问我: 1.Windows Server 2008 计划任务在哪里配置? 2.Windows Server 2008 可以配置每分钟或是每小时执行我的任务吗? 答案是:可以! 首先Window ...
- [CSP-S模拟测试86]题解
好久没有写整套题的题解了呢……主要是这两天考试题愈发神仙 实在是超出了垃圾博主的能力范围啊QAQ A.异或 不难想到,如果我们得到了$[L,R]$中每一位上0和1的个数,那么答案即为$2 \times ...
- mysql捕捉所有SQL语句
MySQL可以通过开通general_log参数(可动态修改)来扑捉所有在数据库执行的SQL语句.显示参数:mysql> show variables like 'general%log%';+ ...