-
2018-08-23 18:40:17
一、引言
当我们在 C++ 中直接像 C 那样使用类的成员函数指针时,通常会报错,提示你不能使用非静态的函数指针:
reference to non-static member function must be called
两个解决方法:
- 把非静态的成员方法改成静态的成员方法
- 正确的使用类成员函数指针(在下面介绍)
关于函数指针的定义和使用你还不清楚的话,可以先看这篇博客了解一下:
https://blog.csdn.net/afei__/article/details/80549202
二、语法
1. 非静态的成员方法函数指针语法(同C语言差不多):
void (*ptrStaticFun)() = &ClassName::staticFun;
2. 成员方法函数指针语法:
void (ClassName::*ptrNonStaticFun)() = &ClassName::nonStaticFun;
注意调用类中非静态成员函数的时候,使用的是 类名::函数名,而不是 实例名::函数名。
三、实例:
#include <stdio.h> #include <iostream> using namespace std; class MyClass { public: static int FunA(int a, int b) { cout << "call FunA" << endl; return a + b; } void FunB() { cout << "call FunB" << endl; } void FunC() { cout << "call FunC" << endl; } int pFun1(int (*p)(int, int), int a, int b) { return (*p)(a, b); } void pFun2(void (MyClass::*nonstatic)()) { (this->*nonstatic)(); } }; int main() { MyClass* obj = new MyClass; // 静态函数指针的使用 int (*pFunA)(int, int) = &MyClass::FunA; cout << pFunA(1, 2) << endl; // 成员函数指针的使用 void (MyClass::*pFunB)() = &MyClass::FunB; (obj->*pFunB)(); // 通过 pFun1 只能调用静态方法 obj->pFun1(&MyClass::FunA, 1, 2); // 通过 pFun2 就是调用成员方法 obj->pFun2(&MyClass::FunB); obj->pFun2(&MyClass::FunC); delete obj; return 0; }
更多相关内容 -
类成员函数作为回调函数的方法及注意点
2019-04-25 11:03:32即因为一个类的静态成员函数调用了类的非静态成员变量,而报错。 下面具体介绍一些相关知识点,以防下次再出错。 类成员函数当回调函数的方法 参考自:...编程中遇到一个错误,提示为error C2597: illegal reference to non-static member
即因为一个类的静态成员函数调用了类的非静态成员变量,而报错。
下面具体介绍一些相关知识点,以防下次再出错。
类成员函数当回调函数的方法
参考自:https://blog.csdn.net/this_capslock/article/details/17001003
方法一:回调函数为普通的全局函数,但在函数体内执行类的成员函数
在创建线程调用回调函数时,传入类对象的指针(比如this指针)作为参数,并在回调函数中把void*强制转换为类的指针(MyClass*),就能使用该指针调用类的成员函数。
这样做的原理是把当前对象的指针当作参数先交给一个外部函数,再由外部函数调用类成员函数。以外部函数作为回调函数,但执行的是成员函数的功能,这样相当于在中间作了一层转换。
缺点:回调函数在类外,影响了封装性。
方法二:回调函数为类内静态成员函数,在其内部调用类的非静态成员函数
此时需要一个指向类本身的、类的静态成员变量指针(static MyClass* CurMy),用来存储当前回调函数调用的对象,相当于法1中给回调函数传入的指针参数。在回调函数中通过CurMy指针调用类的成员函数。
优点:1、解决了法1的封装性问题,
2、没有占用callback的参数,可以从外界传递参数进来
缺点:每个对象启动子线程前一定要注意先让CurMy正确的指向自身,否则将为其它对象开启线程。
方法三:对成员函数进行强制转换,使其作为回调函数
这个方法是原理是,MyClass::func最终会转化成 void func(MyClass *this);即在原第一个参数前插入指向对象本身的this指针。可以利用这个特性写一个非静态类成员方法来直接作为线程回调函数。typedef void* (*FUNC)(void*); FUNC callback = (FUNC)&MyClass::func;
对编译器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)这两种函数指针虽然看上去很不一样,但他们的最终形式是相同的,因此就可以把成员函数指针强制转换成普通函数的指针来当作回调函数。在建立线程时要把当前对象的指针this当作参数传给回调函数(成员函数func),这样才能知道线程是针对哪个对象建立的。
注意:此方法中FUNC函数的参数一定要是void*,这样才能在编译后把this指针转变为MyClass *this。
优点:法3的封装性比法2更好,因为不涉及多个对象共用一个静态成员的问题,每个对象可以独立地启动自己的线程而不影响其它对象。
为什么回调函数必须为静态函数?
普通的C++成员函数都隐含了一个“this”指针参数,当在类的非静态成员函数中访问类的非静态成员时,C++编译器通过传递一个指向对象本身的指针给其成员函数,从而能够访问类的数据成员。也就是说,即使你没有写上this指针,编译器在编译的时候自动加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。
正是由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数匹配失败。所以为了实现回调,类中的成员函数必须舍弃掉隐藏的this指针参数。因此,类中的回调函数必须为静态函数,加上static关键字。
类的静态成员函数如何访问非静态成员?
静态成员不属于某个具体的对象,而是被所有对象所共享。即静态成员属于整个类,不属于具体某个对象;非静态成员属于具体某个对象。因而静态成员函数只能访问类的静态成员,不能访问类中非静态成员。
那么,如何让静态函数访问类的非静态成员?
方法是:对于静态成员函数,我们显示的为其传递一个对象的首地址(该类的指针)。一般在这个静态成员函数的形参列表中加入一个 void* 类型的参数,来保存对象的首地址。并在该函数内部对该参数进行类型转换,通过类型转换后的参数来调用非静态成员。
或者用一个类的全局指针数组,保存每一个创建出来的类的this指针,用全局指针去调用。
-
c++类成员函数做函数参数
2020-10-26 17:47:29在类内部的typedef类内部的typedef函数声明,属于类成员,在类外声明时必须加类声明作用域(Test::FUNC),且赋值的函数必须为类成员(&Test::Func1)
下面的类中,Func1和Func2的返回值类型和参数列表都一样,定义的FUNC类似委托类型
Test.h
#pragma once #include<iostream> using namespace std; class Test { public: typedef void (Test::*FUNC)(int); Test(); ~Test(); void Func(FUNC f, int x); void Func1(int x); void Func2(int x); };
Test.cpp
#include "Test.h" Test::Test() { } Test::~Test() { } void Test::Func(FUNC f, int x) { (this->*f)(x); } void Test::Func1(int x) { cout << "Func1:" << x<<endl; } void Test::Func2(int x) { cout << "Func2:" << x << endl;; }
源.cpp
#include"Test.h" int main() { Test test; test.Func(&Test::Func1, 1); test.Func(&Test::Func2, 2); system("pause"); }
运行结果
-
c++类成员函数指针
2021-01-19 18:04:56提出疑问 首先问大家一句,什么是函数指针? 肯定有的人会这样回答,函数指针?...因为成员函数包括了虚函数和非虚函数(这里涉及虚表问题,可以先简单看看列出的虚函数系列,否则接下来问题会有点难以接受。) 虚函数提出疑问
首先问大家一句,什么是函数指针?
肯定有的人会这样回答,函数指针?不就是指向函数地址的一个指针吗?或者就是一个存放着一个函数首地址的变量?
当然,那些有点底层基础的肯定会这样说,函数就是一堆连续的机器码,而函数指针,就是存放了这堆连续机器码首地址的变量。
详细了解函数指针(因为这里主要讲成员函数指针原理):
c/c++函数指针(Hook前奏1)
c/c++ typedef定义函数指针(Hook前奏2)
那么大家是不是回答的时候,考虑的地方是不是仅仅局限于 一般的函数????那么成员函数呢???
为什么得强调成员函数呢?因为成员函数包括了虚函数和非虚函数(这里涉及虚表问题,可以先简单看看列出的虚函数系列,否则接下来问题会有点难以接受。)
虚函数系列:
详解虚函数的实现过程之初探虚表(1)
详解虚函数的实现过程之单继承(2)
详解虚函数的实现过程之多重继承(3)
详解虚函数的实现过程之虚基类(4)
详解虚函数的实现过程之菱形继承(5)c++层面
首先,上两份成员函数指针代码
非虚函数
#include<iostream> using namespace std; class a { public: int add(int a, int b) { return a + b; } }; typedef int (a::* pClassFun)(int, int); int main() { pClassFun pointer = &a::add; a aa; cout << (aa.*pointer)(10, 20); }
虚函数
#include<iostream> using namespace std; class a { public: virtual int add(int a, int b) { return a + b; } }; typedef int (a::* pClassFun)(int, int); int main() { pClassFun pointer = &a::add; a aa; cout << (aa.*pointer)(10, 20); }
一般:
int (*pointer)(int, int); // 声明函数指针
这里,pointer指向的函数类型是int (int, int),即函数的参数是两个int型,返回值也是int型。
注:pointer两端的括号必不可少,如果不写这对括号,则pointer是一个返回值为int* 的函数。成员函数指针:
int (A::*pf)(int, int); // 声明一个成员函数指针
,这里A::*pf两端的括号也是必不可少的,如果没有这对括号,则pf是一个返回A类数据成员(int型)指针的函数。
注意:和普通函数指针不同的是,在成员函数和指向该成员的指针之间不存在自动转换规则。
pf = &A::add;
//正确:必须显式地使用取址运算符(&)
pf = A::add;
// 错误当我们初始化一个成员函数指针时,其指向了类的某个成员函数,但并没有指定该成员所属的对象——直到使用成员函数指针时,才提供成员所属的对象。
仔细观察后,除了一个有virtual,一个没有virtual,其它的都一样。是的,事实的确如此。ida动调分析
我把所写的代码,然后生成可执行程序后,拖入
ida
进行分析一番
首先分析非虚函数
看了一下,它俩的汇编代码,主要是经过一个判断跳,如果是虚函数的话,那么它的寻址是比较复杂的(即跳到那个寻址比较多的代码),如果是非虚函数的话,那么它的寻址是超级简单
非虚函数执行完call _main 之后直接去取出 这个成员函数的地址
地址里面直接是函数的主体,毫无疑问,取出来的就是函数地址虚函数
看了一下反编译代码差不多,找到关键点(同一个位置的判断跳转,只是命名不同),jz short loc_401427
,而这个判断跳转取决于上面取出来的eax
值,接下来动调一下,虚函数执行完call _main 之后又多执行了几行代码
.text:004013FA mov [ebp+var_10], 1 .text:00401401 mov [ebp+var_C], 0 .text:00401408 lea eax, [ebp+var_28] .text:0040140B mov [esp], eax .text:0040140E call __ZN1aC1Ev ; a::a(void)
然后取出地址的时候,并非直接取,而是又调用了一个函数,进行一系列的操作
call __ZN1aC1Ev
:00410EFC push ebp .text:00410EFD mov ebp, esp .text:00410EFF mov eax, [ebp+arg_0] .text:00410F02 mov dword ptr [eax], offset off_4469B8 .text:00410F08 pop ebp .text:00410F09 retn
然后取地址也就这一行
mov dword ptr [eax], offset off_4469B8
但是我们跟随一下,它是不是函数主体呢?
毫无疑问,不是。。。。所以这里取出来的是虚表的地址,再进一层,所以这个4469B8
是虚表的地址。
才找到函数主体而且我们要看的是返回值,因为只有返回值才会赋值给我们的函数指针,并非看[eax],而是看eax,
执行完.text:00410EFF mov eax, [ebp+arg_0]
发现eax
里面的值是0x64ff18
,看了一下也就是指针的地址。。
(就是从函数外面把函数指针的地址当做参数传进去,然后把虚表地址 放在指针里面,也就是把它当做虚表指针来使用。。记住,这个指针里面存放的不是函数的地址,而是虚表地址。)然后指针里面的值存放的是虚表的地址,虚表里面又放着虚函数的地址。
edx
里面存放的是虚表的地址,然后再进行取内容,即取到了函数的地址,作为参数传过去。至于这加减操作,也就是虚表里面不一定只存放着一个虚函数的地址。。因为eax存放着偏移+1值,这里eax刚开始是既可以用作判断跳转,又可以当虚表偏移,所以加上eax值后必须自减1,回到正确的偏移近一步解释
c++代码
#include<iostream> using namespace std; class a { public: virtual int add(int a, int b) { return a + b; } virtual int decrease(int a, int b) { return a - b; } }; typedef int (a::* pClassFun)(int, int); int main() { pClassFun pointer = &a::add; a aa; cout << (aa.*pointer)(10, 20); pClassFun pointer1 = &a::decrease; cout << (aa.*pointer1)(20, 10); }
ida调试
加了一个虚函数,加了一个函数指针指向它,接下来我们来看看是不是另外一个虚函数是不是紧接着放在虚表的中的第一个虚函数地址后面。如果是的话,那么另外一个函数指针就直接没用了,它直接通过第一个函数指针值加一个偏移再取内容就ok啦!
第一部分和上面一样。
第二部分是不是没有去再一次寻址了,也就是没有下面这个calleax存放的是偏移加1值,然后一个指针是4个字节,第一个指针的偏移是0 ~ 3,第二个也就是4 ~ 7,eax存放起始偏移+1,所以也就是5
这里的意思不就是取出虚表地址,再取出函数地址相应的偏移,然后再取一次内容吗?(也就是取函数的地址)
注意
dec
是自减,也就是加上了5,但是偏移是4,所以减1,即第二个指针!!!因为eax存放着偏移+1值,这里eax刚开始是既可以用作判断跳转,又可以当虚表偏移,所以加上eax值后必须自减1,回到正确的偏移 -
详解函数指针和类成员函数指针
2017-11-12 21:35:56我觉得要理解这个问题,以及要理解后面的函数指针和类成员函数指针,没有什么比从计算机原理的角度来理解更容易了。这里就简要回顾一下相关知识。 众所周知,计算机(图灵机)执行程序的基本流程就是:取指令->执行... -
类成员函数作为线程函数使用
2017-01-11 11:37:50类成员函数作为线程函数使用 C++类成员函数使用时,都会隐式传递一个this指针给该函数,this指针指向该类的对象...那么一般的类成员函数是不能用作回调函数的,因为库函数在使用回调函数时,都会传递指定的符合回调函数 -
C++中类和对象以及成员函数
2017-11-17 16:57:23一个实体拥有自己的属性和行为,属性是私有的,行为是共有的,在C++中实体对应的就是对象,实体抽象数据类型就是类,属性是成员变量,行为是成员函数。 面向对象思想的三大特征: 封装、继承、多态(静多态,动多态... -
C++-使用类(作为成员函数还是非成员函数)
2018-07-06 21:26:44一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。 例如,Time类的加法运算符在Time类声明中的原型如下: Timeoperator+(constTime & t) const; //成员函数版本 这个类也可以使用... -
浅谈C++类中6个成员函数
2021-03-18 15:45:13构造函数是一个特殊的成员函数,名字与类名相同且不能有返回值,创建类类型时由编译器自动调用,在对象的生命周期内只调用一次。**主要任务是初始化对象。 ↓下面是一个简单的构造函数(全缺省): 主函数初始化时... -
类模板及其成员函数的定义及注意事项
2020-12-18 02:12:02在类模板(及其成员函数)的定义中,我们将模板参数当作替身,代替使用模板时用户提供的类型或值。 代码示例: template <typename T> //用T这个模板类型参数,来表示A中保存的元素的类型。 //当用户实例化A... -
C++一个类的成员函数作为另一个类的友元函数
2019-04-27 18:17:07定义了两个类,都有私有变量num。分别用全局函数、友元函数计算两者的和。 #include<iostream> using namespace std; class B; class A; // 此行可不加,这里加此行是因为下面举例子有用 class A {... -
C++ 函数指针 & 类成员函数指针
2013-11-21 21:17:02一、函数指针 函数存放在内存的代码区域内,它们同样有地址.如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。 1、函数指针的定义方式:... -
类的static成员函数与普通成员函数的区别
2017-12-15 15:53:29成员函数:无论静态函数还是非静态函数,都是属于类的(这一点与数据成员的静态非静态不同),对象并不拥有函数的拷贝.两者的区别在于:非静态的函数由类对象(加.或指针加->;)调用,这时将向函数传递this指针.而静态函数由... -
运算符重载函数作为类成员函数与友元函数的区别
2016-03-16 10:49:32运算符重载函数作为类成员函数与友元函数 -
c++---类和对象(六大默认成员函数)
2019-05-22 11:38:26类中默认的六个成员函数 构造函数 析构函数 拷贝构造函数 赋值操作符重载 取地址和const取地址操作符重载 const成员函数 1. 类中默认的六个成员函数 首先看看下面代码 class A{ }; int main(){ A a; return 0; } ... -
【C++】C++运算符重载(成员函数实现、友元函数实现)
2018-06-10 20:29:03运算符重载 对于面向对象的程序设计来说,运算符重载可以完成两个对象之间的复杂操作...为了重载运算符,首先要定义运算符重载函数,它通常是类的非静态成员函数或者友元函数,运算符的操作数通常也应为对象。 定... -
C++ 重载运算符 运算符重载函数作为类成员函数 重载运算符+
2017-09-20 19:27:31用运算符重载函数作为类成员函数的方法重载运算符+ 下面的例子来自于课本: #include using namespace std; class Complex { public: Complex() //无参构造函数 { real = 0; imag = 0; } Co -
python 类成员函数
2016-10-08 14:30:44出处: ... 1.关于定义类的一些奇特之处 今天在Python中定义一个类,很奇怪,不需要事先声明它的成员变量吗?暂时不知,先记录下来: class Account(object): "一个简单的类" account_ -
C++类成员函数转换成函数对象
2016-09-15 19:39:27C++中,类的成员函数(member_function)通常不能直接作为函数对象来使用,最常见的就是创建线程时,不能使用非静态的成员函数来初始化一个线程。 这个主要是因为没有传入this指针,而下面的转换或者绑定,本质是将类... -
C++ 类成员函数可以访问所有类对象的私有数据
2020-06-22 15:25:29众所周知,类的私有变量是无法在类外直接访问的,只能通过类的成员函数访问。 且看下面一段代码: class Stock { private: double total_val;//这是私有的哦~ public: Stock();//默认构造函数 Stock(const ... -
虚函数与静态成员函数
2019-06-02 22:04:54C++中,静态成员函数不能被声明为virtual函数。 例如,下面的程序会编译失败。 #include class Test { public: // 编译错误:static成员函数不能声明为virtual virtual static void fun() { } }; 同样地,静态成员... -
QT中类的成员函数作为回调函数
2018-03-09 12:16:15这里主要实现的功能:需要设计一个插件,把插件内的数据通过函数指针参数的方式传递到另外一个类中,显示出来,使用回调函数的方式 http://blog.csdn.net/ksn13/article/details/40538083,代码的逻辑和上述网站的... -
成员函数的定义
2020-05-06 17:03:55实际上,成员函数和方法指的是同一种实体, 是一种实体的两种不同叫法, 成员函数是程序设计语言 C + + 中的术语,而方法是面向对象方法中的术语。在以后的叙述中, 本书采用术语成员函数。 成员函数的定义通常采用两种... -
Point类(常成员函数,友元函数)
2021-01-09 17:10:24【问题描述】根据下面的主函数,补充定义点类Point及相关函数,主要成员如下: 1、两个double型私有数据成员x,y,分别表示横坐标和纵坐标 2、几个公有成员函数 (1)构造函数:带有默认值,横坐标和纵坐标的默认值均... -
运算符重载函数作为类成员函数和友元函数
2013-09-22 23:17:43运算符重载函数既可以做为类成员函数也可以重载为友元函数,但使用定义方法和使用上是由较大差别的。 运算符重载函数作为类成员函数 首先看一个运算符重载函数作为类成员函数的示例代码: 定义Complex为复数类,... -
C++命名空间中类声明、成员函数声明和函数模板
2019-08-27 10:11:26命名空间是一个范畴,它包含类声明,函数声明,常量声明和模板声明等名字空间成员。本文拟讨论如何在名字空间中声明自己的类和函数,以及如何在程序中使用它们。 在使用C++类时,如果用到命名空间,在使用的时候需要... -
C++中一个类成员函数调用另一个类成员的方法
2018-10-31 23:13:01在C++中一个类成员函数调用另一个类成员的方法主要有:类的组合,友元类,单例模式等,下面主要讲讲这三种方法的实现 方法1:利用类的组合 组合通俗来讲就是类B有类A的属性,如声明一个Person类,再声明一个... -
C++函数编译原理和成员函数的实现
2017-07-25 15:34:54对象的内存中只保留了成员变量,除此之外没有任何其他信息,程序运行时不知道 stu 的类型为 Student,也不知道它还有四个成员函数 setname()、setage()、setscore()、show(),C++ 究竟是如何通过对象调用成员函数... -
C++ --类的默认成员函数是否可以被定义为虚函数
2018-05-27 20:21:061.因为虚函数是存放在对象的虚表里面,如果将构造函数定义为虚函数,则构造函数也必须存放在虚表里面,但是此时对象都还没有创建也就没有所谓的虚表。 2.不将构造函数定义为虚函数,对象模型如下: 3.如果将构造... -
C++中成员函数,非成员函数和友元函数
2016-11-20 11:18:06转自:http://blog.chinaunix.net/uid-10673338-id-2936852.html 转自:... 对以上两篇文章,我添加了自己已有的部分知识,并重新地汇总整理 ... ...从函数定义的位置来粗略理解