[C++] 使用指针调用类成员方法_类指针调用成员函数-程序员宅基地

技术标签: c++  开发语言  

关键词: 类成员方法 函数指针 类间函数调用  std::function


目录

结论

摘要

0 引子

1 使用静态成员方法

2 使用this-> 传递函数指针

3 函数包裹

3.1 C语言形式的包裹(指针包裹)

3.2 C++ 形式的包裹

3.3 std::funtion 包裹(优雅的实现)

4 拓展

4.1 std::funtion 详解

4.2 成员函数指针长度

5 应用

5.1 多线程创建

5.2 接收与处理分离


结论

任何指向“类”的成员函数指针,由于携带额外的所属对象信息,与一般的函数有根本的不同,不能直接用来进行函数调用。用C++ 描述:对于类class MyClass 中的成员函数 void Method(); ,它的函数类型为void (MyClass::*f)(); 而不是void (*f)(); 。

摘要

对于C语言来说,函数指针是可以任意传递的。这跟语言特性有关系,C语言是一种静态的,在程序运行前分配函数地址的。而C++是动态的,在对象申请时,才会有成员函数的地址。因此,对于C++使用者而言,一个类中方法是不能通过直接传递的方式获取另外一个类的成员函数指针的。你在传递成员函数的地址时,还要传递该对象的指针。

0 引子

在C语言中,你可以转换任意函数的指针,通过传递函数指针的方式将目标函数传递给目标调用。函数指针类型是全局的,任意传递的。它是一种不够安全的方法。比如:

#include <iostream>

void Add(int a, int b)
{
    std::cout << a+b;
}

int main()
{
    auto f = Add;
    f(1, 2);  
    return 0;
}

在C++ 中,方法通常被类包裹。类成员方法包含类名称。比如:

class MyClass
{
public:
    MyClass();
    ~MyClass();
    void MyClassMethod();
private:
};

如果你需要使用成员函数指针,那么你可以这样做。

1 使用静态成员方法

将类的成员方法声明为静态的,该方法归属于类而非对象。在程序运行前分配地址,因此,这是一种更像C语言的使用方法。通过

&类::成员方法

可以获取到函数指针。

MyClass::MyClass(){}
MyClass::~MyClass(){}

void MyClass::MyClassMethod(){ std::cout << "Hello"; }

int main()
{
    auto f = &MyClass::MyClassMethod;
    f();
    return 0;
}

注意:静态成员方法调用的方法必须是静态的。

2 使用this-> 传递函数指针

如果指针在同一个类中传递,比如:

#include <iostream>

class MyClass
{
public:
    MyClass();
    ~MyClass();
    void MyClassMethod();
private:
    void InvokeMethod();
};

MyClass::MyClass(){}

MyClass::~MyClass(){}

void MyClass::MyClassMethod()
{
    auto  f = &MyClass::InvokeMethod;
    (this->*f)();
}

void MyClass::InvokeMethod()
{
    std::cout << "Hello";
}

int main()
{
    MyClass my_class;
    my_class.MyClassMethod();
    return 0;
}

3 函数包裹

一个类调用另一个类的函数指针的时候可以采用函数包裹的方式,具体有如下三种

3.1 C语言形式的包裹(指针包裹)

本质上是将多级指针转换为1级指针。对于一个对象的成员函数的方法,通过将二级指针调用,封装成C语言函数调用。

#include <iostream>

typedef void(*f)();

void function(InvokeClass* myIncvokeClass)
{
    myIncvokeClass->InvokeMethod();
}

class MyClass
{
public:
    MyClass();
    ~MyClass();
    void TestMethod(f myFunction);
private:
};

MyClass::MyClass(){}

MyClass::~MyClass(){}

void MyClass::TestMethod(f myFunction)
{
    myFunction();
}

class InvokeClass
{
public:
    InvokeClass();
    ~InvokeClass();
    void InvokeMethod();
private:
};

InvokeClass::InvokeClass(){}

InvokeClass::~InvokeClass(){}

void InvokeClass::InvokeMethod()
{
    std::cout << "hello";
}

int main()
{
    auto myIncvokeClass = new InvokeClass();
    auto f = function;
    f(myIncvokeClass);
    return 0;
}

3.2 C++ 形式的包裹

#include <iostream>

class InvokeClass
{
public:
InvokeClass();
~InvokeClass();
void InvokeMethod();
private:
};

InvokeClass::InvokeClass(){}

InvokeClass::~InvokeClass(){}

void InvokeClass::InvokeMethod()
{
std::cout << "hello";
}

class MyClass
{
public:
MyClass();
~MyClass();
void TestMethod(InvokeClass* myInvokeClass);
private:
};

MyClass::MyClass(){}

MyClass::~MyClass(){}

void MyClass::TestMethod(InvokeClass* myInvokeClass)
{
myInvokeClass->InvokeMethod();
}

int main()
{
MyClass myClass;
auto  myInvokeClass = new InvokeClass;
myClass.TestMethod(myInvokeClass);
}

因2中的声明为 void TestMethod(InvokeClass* myInvokeClass); 只能传递一个类对象指针

可将2中声明为

template <typename T>
void TestMethod(T* myInvokeClass);

或者

void TestMethod(InvokeClass1* myInvokeClass);
void TestMethod(InvokeClass2* myInvokeClass);
void TestMethod(InvokeClass3* myInvokeClass);

3.3 std::funtion 包裹(优雅的实现)

#include <iostream>
#include <functional>

class MyClass
{
public:
    MyClass();
    ~MyClass();
public:
    void Method(std::function<int(int, int)> f);
};

MyClass::MyClass(){};
MyClass::~MyClass(){};

void MyClass::Method(std::function<int(int, int)> f)
{
    std::cout << f(1, 2);
}

class InvokeClass
{
public:
    InvokeClass();
    ~InvokeClass();
    int Add(int i, int j) {
        return i + j;
    }
private:
};

InvokeClass::InvokeClass(){}

InvokeClass::~InvokeClass(){}

int main()
{
    InvokeClass myInvokeClass;
    MyClass myClass;
    std::function<int(int, int)> f = bind(&InvokeClass::Add,\\
                        &myInvokeClass,std::placeholders::_1, std::placeholders::_2);   
    myClass.Method(f);
    return 1;
}

4 拓展

4.1 std::funtion 详解

std::function详解_在座的各位都是高手的博客-程序员宅基地_std::functionstd::function详解https://blog.csdn.net/weixin_44378800/article/details/115210731

        std::funtion 函数包装器模板,函数包裹器,将可调用对象包裹后只保留特征。

4.2 成员函数指针长度

注意:在传递成员函数的地址时,要传递该对象的指针。它是一个二级指针,隐式的转换可能存在风险,在更高级的 C++ 的编译器中,二级指针是不允许被隐式的转换的,如果你要使用它,请使用显示的表达法。在下面的程序中,写一个简单的测试函数查看各种类的成员函数指针和普通函数指针的长度并输出到屏幕上。

#include <iostream>
class MyClass;  
class MyClass2{};
class MyClass3   //一个有定义的类。
{
public:
    void (* memberfun)();
    void Memberfun1( void (* f2)( ) ) { f2( ) ;} //成员函数1调用成员函数//2。
    void Memberfun2( );//成员函数2。
};

class MyClass4: virtual MyClass3 ,MyClass2  //一个有virtual继承的类(derivative class)。
{
public:
               void Memberfun1( void (* f2)( ) ) { f2( ) ;}
};

class MyClass5: MyClass3,MyClass2  //一个继承类(derivative class)。
{
               public:
               void Memberfun1( void (* f2)( ) ) { f2( ) ;}
};

int main()
{
    std::cout << "一般函数指针长度= " << sizeof(void(*)()) << '\n';
    std::cout <<"-类的成员函数指针长度-"<<'\n'<<'\n';
    std::cout <<"MyClass3类成员函数指针长度="<< sizeof(void(MyClass3::*)())<<'\n'<<'\n';
    std::cout <<"MyClass5类成员函数指针长度="<<sizeof(void (MyClass5:: *)())<<'\n';
    std::cout <<"MyClass4类成员函数指针长度="<<sizeof(void (MyClass4:: *)())<<'\n';
    std::cout <<"MyClass类成员函数指针长度="<<sizeof(void(MyClass::*)()) <<'\n';
    return 0;     
  
}

输出结果:

一般非成员函数指针长度= 4

类的成员函数指针长度:

MyClass3类成员函数指针长度=4

MyClass5类成员函数指针长度=8

MyClass4类成员函数指针长度=12

MyClass类成员函数指针长度=16

以上结果表明,一般函数指针的长度为4个字节,而类的成员函数指针的长度随类的定义与否、类的继承种类和关系而变,从无继承关系类(MyClass3)的4字节(32位)到有虚继承关系类(Virtual Inheritance)(MyClass4)的12字节(96位),仅有说明(declaration)没有定义的类(MyClass)因为与其有关的一些信息不明确成员函数指针最长为16字节(128位)。显然,与一般函数指针不同,指向“类”的成员函数的指针不仅包含成员函数地址的信息,而且包含与类的属性有关的信息。因此,一般函数指针和类的成员函数指针是根本不同的两种类型。当然,也就不能用一般函数指针直接调用类的成员函数。尽管使用较早版本的编译软件编译仍然可以通过,但这会给程序留下严重的隐患。

5 应用

5.1 多线程创建

向 std::thread 传递成员函数指针。同时需要传递该对象的指针。

#include <iostream>
#include <thread>

class MyClass
{
public:
    MyClass();
    ~MyClass();
    void TestMethod();
private:
};

MyClass::MyClass(){}

MyClass::~MyClass(){}

void MyClass::TestMethod()
{
    std::cout << "hello";
}

int main()
{
    auto myClass = new MyClass();
    auto myThread = std::thread(&MyClass::TestMethod,myClass);
    return 0;
}

5.2 接收与处理分离


#include <iostream>
#include <thread>

//TODO
class Rcv
{
public:
Rcv();
~Rcv();
private:
};

Rcv::Rcv(){}

Rcv::~Rcv(){}

//TODO
class Handle
{
public:
Handle();
~Handle();
private:
void Handle1();
};

Handle::Handle(){}

Handle::~Handle(){}

int main(){
    //TODO

}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_18826027/article/details/127767539

智能推荐

TransH论文翻译-程序员宅基地

文章浏览阅读1.2k次。TransH全文翻译_transh

Redis重启_重启redis-程序员宅基地

文章浏览阅读2w次。1、启动redis服务。_重启redis

如何给Alpine Linux安装curl_alpine linux curl-程序员宅基地

文章浏览阅读1.1w次,点赞5次,收藏6次。/etc # cat *release3.10.5NAME="Alpine Linux"ID=alpineVERSION_ID=3.10.5PRETTY_NAME="Alpine Linux v3.10"HOME_URL="https://alpinelinux.org/"BUG_REPORT_URL="https://bugs.alpinelinux.org/"/etc #给alpine安装curl1.设置国内镜像源sed -i 's/dl-cdn.alpinel..._alpine linux curl

Stata 外部命令:最常用和最新的命令_corr2docx用法-程序员宅基地

文章浏览阅读2.6w次,点赞5次,收藏51次。时至今日,stata 已经发布了第15版,功能不断增强。然而,勤奋的 stata 用户们每天仍然在开发新的程序,不断缩小理论计量与实际应用之间的差距。下面,我们就梳理一下 stata 外部命令的相关资源,包括:从哪些地方获取外部命令?哪些外部命令是最常用的,最流行的?哪些是最新发布的?关注一下这些外部命令,可以大幅提高我们的分析效率。_corr2docx用法

Ubuntu11.04 sudo apt-get install flex无法更新的问题解决方法-程序员宅基地

文章浏览阅读2.2k次。今天Ubuntu11.04安装SimpleScalar时,使用sudo apt-get install flex-old bison gedit更新一些软件时,发现不能更新了(所悟关键词为404 not found和“无法下载”之类的一大堆)。直入主题,解决方法就是为其添加修改更新源的网址。输入以下命令:sudo gedit /etc/apt/sources.list就能在_sudo apt-get install flex

datafram 怎么去掉表头 python pandas...._dataframe去掉表头-程序员宅基地

文章浏览阅读10w+次,点赞3次,收藏8次。实现增加参数header=None即可举两个例子:dataframe = pd.read_csv("test.csv",header=None)和dataframe=pd.read_table('test.txt',header=None)原理header : int or list of ints, default ‘infer’指定行数用来作为列名,数据开始行数。如果文件中没有列名,则默认为0,否则设置为None如果明确设定header=0 就会替换掉原来_dataframe去掉表头

随便推点

SVG 剪裁与蒙版(clipPath & mask)_svg 图片 异形蒙板 path-程序员宅基地

文章浏览阅读4.5k次。简介参考MDN https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath The SVG element defines a clipping path. A clipping path is used/referenced using the clip-path property. The_svg 图片 异形蒙板 path

基于JavaFX的端口冲突检测工具_java实现端口冲突检测-程序员宅基地

文章浏览阅读758次。JavaFX实战项目:端口冲突检测工具_java实现端口冲突检测

新书推荐 |《深入以太坊智能合约开发》-程序员宅基地

文章浏览阅读756次。新书推荐《深入以太坊智能合约开发》长按二维码了解及购买这是一部能指导读者从入门到进阶的以太坊智能合约开发指南。HiBlock区块链技术社区官方出品,4位资深区块链技术专家联合撰写。编辑推..._深入理解以太坊 pdf

PostgreSQL9.6.主从复制配置_postgresql9.6默认复制解码器-程序员宅基地

文章浏览阅读327次。PostgreSQL做主从复制,两台服务器安装相同的pg,前提条件:版本:9.6.19IP: 172.23.22.201(master) 172.23.22.202(standby)安装:/usr/pgsql-9.6数据:/var/lib/pgsql/9.6/datamaster 主库配置# 登录Master库,创建用于复制数据具有replication权限的用户[root@localhost 9.6]# su - postgresLast login: Wed Dec 18 21:14_postgresql9.6默认复制解码器

关于CUDA10.1和CUDNN的下载与安装。_cuddn10.1下载-程序员宅基地

文章浏览阅读544次。目录前言安装测试是否成功前言因为要引入tensorflow这个库,那么久不可避免的要使用CUDA和CUDNN。本来应该是没必要写一篇博客的,但实在是,CUDNN的下载太麻烦了。我是弄了好几个晚上才直到今天完成。之前它要求要填信息,填了,但也进不去。所以,这里把参考的那个博客放过来。给你们进行参考,应该可以基本完成这个的下载安装。安装Win10安装CUDA10和cuDNN.Windows 10 下安装CUDA10.1 + CUDNNWindows下 TensorFlow 的安装(包含:CUP版、_cuddn10.1下载

gitstack破解 安装_gitstackcrack zip-程序员宅基地

文章浏览阅读5.7k次。gitstack 2.3.6 安装 破解 教程_gitstackcrack zip

推荐文章

热门文章

相关标签