0 前言

就像C++其他类型一样,函数也拥有指针,不过不得不说C++和C的函数指针非常抽象,语法空前绝后。加之C++有C的一面,有面向对象的一面,还有面向模板的一面,在《Effective C++》里,作者第一条就点明题意,不能把C++当成1种语言来看,而是4种,每种语言都有独特的风情,而混合起来,你甚至得学习一点密码学...

接下来这段代码(来自小彭老师),核心功能是注册GLFW的回调函数,即接受用户的键盘输入,变换相机位姿进行模型显示。

但看起来却让人望而却步。下面将对此代码进行解读。

template <class, class ...Ts>
static void (*_impl_glfw_input_callback(void (InputCtl::*pFn)(Ts...)))(GLFWwindow *, Ts...) {
static void (InputCtl::*gpFn)(Ts...);
gpFn = pFn;
return [] (GLFWwindow *window, Ts ...args) -> void {
auto game = (Game *)glfwGetWindowUserPointer(window);
if (game) [[likely]] {
(game->m_inputCtl.*gpFn)(args...);
}
};
} template <class FpFn>
static auto glfw_input_callback(FpFn fpFn) {
return _impl_glfw_input_callback<FpFn>(fpFn());
} // usage
glfwSetCursorPosCallback(window, glfw_input_callback([] { return &InputCtl::cursor_pos_callback; }));

1 Function Pointer in C/C++ type

1.1 ordinary function Pointer

以下这段代码来自 Author Vysandeep3

// C++ program for the above approach
#include <iostream>
using namespace std; void demo(int& a)
{
a += 10;
} // Driver Code
int main()
{
int num = 20; // Now ptr contains address of demo
// function or void
void (*ptr)(int*) = &demo; // or (*ptr)(num);
ptr(num); cout << num << endl; return 0;
}

returnType (*function_pointer_name)(Type a, Type b, Type ... n)

其中 function_pointer_name 定义了一个变量,他可以存储类似 returnType XXXX(Type a, Type b, Type ... n) 这种形式函数的指针。

但是有些时候我们有多个这种类型的函数,例如

int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int rat(int a, int b); int (*ptr)(int, int) = NULL;
if(a == b) {
ptr = &add;
}else{
ptr = &mul;
}

我们需要在main()函数里决定什么时间什么条件一个这种类型的指针指向的函数,需要一段代码来完成这种操作。

问题是,我们可不可以写一个函数来完成这种操作呢?这也是一种重构的思想,当一段代码可能需要用到多次的时候,为什么不把他写成一个函数呢?

1.2 non-static member function of class

Its type is int (Fred::*)(char,float) if a non-static member function of class Fred

Note: if it’s a static member function of class Fred, its type is the same as if it were an ordinary function: “int (*)(char,float)”.

https://isocpp.org/wiki/faq/pointers-to-members

float (SomeClass::*my_memfunc_ptr)(int, char *);
// For const member functions, it's declared like this:
float (SomeClass::*my_const_memfunc_ptr)(int, char *) const; my_memfunc_ptr = &SomeClass::some_member_func;
// This is the syntax for operators:
my_memfunc_ptr = &SomeClass::operator !; // There is no way to take the address of a constructor or destructor

给出一篇学习资料: Member Function Pointers and the Fastest Possible C++ Delegates by Don Clugston

1.3 Lambda To Function Pointer

#include <iostream>
using namespace std;
#define PI(x) x, #x, x##x auto noCapture =
[](int res) -> float
{
std::cout << "No capture lambda called with " << res << "\n";
return 99.9f;
}; typedef float(*NormalFuncType)(int); int main(){
NormalFuncType noCaptureLambdaPtr = noCapture; //----------- (1)
float res = noCaptureLambdaPtr(100); //----------- (2)
return 0;
} // COUT
// No capture lambda called with 100

注意这东西的地址需要用 auto noCapture = [](int res) -> float{} 来接。除此之外,就当成一个普通的函数指针就行

给出一篇学习资料: How To Bind Lambda To Function Pointer

1.4 总结什么是指针

int* pInt;
char* pChar;

一个指针,指向一块内存中的地址(存储地址)。但是同时他又有对应的类型,char* 意为从这个地址开始读取1个字节,int* 意为从这个地址开始读取4个字节。这就是指针的核心。指针类型决定了程序如何对待一个地址。

另外C语言可以通过2个指针实现面向对象编程。当然正常的面向对象编程也是需要2个指针(*this, *underThis)。想要深入了解的话,可以搜索 opaque-pointers 这方面的知识。

给出一篇学习资料: Practical Design Patterns: Opaque Pointers and Objects in C

2 Returning a function pointer from a function in C/C++

以下这段代码来自 Author Vysandeep3

#include <iostream>
using namespace std; int add(int a, int b) {
return a + b;
} int subtract(int a, int b) {
return a - b;
} int (*get_operation(char op))(int, int) {
if (op == '+') {
return &add;
} else if (op == '-') {
return &subtract;
} else {
return NULL;
}
} int main() {
int (*op)(int, int) = get_operation('+');
int result = op(3, 4);
cout << "Result: " << result << endl;
return 0;
}

int (*get_operation(char op))(int, int):

  • 其中 get_operation(char op) 是一个返回函数指针的函数
  • int (*) (int, int) 是返回的函数指针所指向的函数类型

这东西看起来确实很怪..., 但是我们只能接受。

这里给出一种理解方式, 首先一个指针需要两个标识符 Type* ptr_name

int* ptr;       // ptr is a pointer to an integer

int(*)(int, int);	// key idea: function pointer type

// ptr lost a pointerType like int*
int (*ptr)(int, int); // ptr is a pointer to a function that takes that takes two arguments and returns an integer // int(*)(int, int) ptr; //---------------------------------------------------------------------// int ptr(char op); // ptr is a function that takes that takes one char type argument and returns an integer // ptr() lost a returnType like int
int (*ptr(char op))(int, int){}; // ptr() is a function that takes one char argument returns a pointer to a function which two arguments and returns an integer. // int(*)(int, int) ptr(char op) {};

https://www.learncpp.com/cpp-tutorial/introduction-to-pointers/

3. C - Variable Arguments (Variable length arguments)

printf("Some values: %d, %s, %c!", 4, "foo", 'z')

#include <stdarg.h>

void my_printf(char* format, ...)
{
va_list argp;
va_start(argp, format);
while (*format != '\0') {
if (*format == '%') {
format++;
if (*format == '%') {
putchar('%');
} else if (*format == 'c') {
char char_to_print = va_arg(argp, int);
putchar(char_to_print);
} else {
fputs("Not implemented", stdout);
}
} else {
putchar(*format);
}
format++;
}
va_end(argp);
}

The C library macro void va_start(va_list ap, last_arg) initializes ap variable to be used with the va_arg and va_end macros. The last_arg is the last known fixed argument being passed to the function i.e. the argument before the ellipsis.

https://www.tutorialspoint.com/cprogramming/c_variable_arguments.htm

https://jameshfisher.com/2016/11/23/c-varargs/

https://www.tutorialspoint.com/c_standard_library/c_macro_va_start.htm

4. Variadic Template

C++ Primer P700.

这个东西说白了,就是类似C - Variable Arguments,可以接收任意长度的函数参数,不过与C - Variable Arguments这种需char* format来自己告知函数对应参数的类型。Variadic Template 会自动生成相应的函数定义以及声明,这是模板编程的优势。详情看下面的实例代码。

// Args is a template parameter pack; rest is a function parameter pack
// Args represents zero or more template type parameters
// rest represents zero or more function parameters
template <typename T, typename... Args>
void foo(const T &t, const Args& ... rest); int i = 0; double d = 3.14; string s = "how now brown cow";
foo(i, s, 42, d); // three parameters in the pack
foo(s, 42, "hi"); // two parameters in the pack
foo(d, s); // one parameter in the pack
foo("hi"); // empty pack

the compiler will instantiate four different instances of foo:

void foo(const int&, const string&, const int&, const double&);
void foo(const string&, const int&, const char(&)[3]);
void foo(const double&, const string&);
void foo(const char(&)[3]);

In each case, the type of T is deduced from the type of the first argument. The

remaining arguments (if any) provide the number of, and types for, the additional

arguments to the function.

#include<iostream>
using namespace std; template<typename ... Args> void g(Args ... args) {
cout << sizeof...(Args) << endl; // number of type parameters
cout << sizeof...(args) << endl; // number of function parameters
} int main(){
g(1,2,3,4);
return 0;
} /*
* 4
* 4
*/

5 Variadic Template with member function pointer

当 Variadic Template 来接收 member function pointer时,不需要显式的声明成员函数的参数类型,编译器会自动推导。

#include <cstdio>
class A{
public:
void func(int xpos, int ypos);
}; void A::func(int xpos, int ypos){
printf("Hello World!");
} template <class ...Ts>
void (* Test(void (A::*pFn)(Ts...)))(Ts ...){
return nullptr;
}; /* First instantiated from: insights.cpp:19 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void (*Test<int, int>(void (A::*pFn)(int, int)))(int, int)
{
return nullptr;
}
#endif
; int main()
{
A a;
Test(&A::func); // line == 19
return 0;
}

https://cppinsights.io/

https://adroit-things.com/programming/c-cpp/how-to-bind-lambda-to-function-pointer/

https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible

6 最终解析

template <class, class ...Ts>
static void (*_impl_glfw_input_callback(void (InputCtl::*pFn)(Ts...)))(GLFWwindow *, Ts...) {
static void (InputCtl::*gpFn)(Ts...);
gpFn = pFn;
return [] (GLFWwindow *window, Ts ...args) -> void {
auto game = (Game *)glfwGetWindowUserPointer(window);
if (game) [[likely]] {
(game->m_inputCtl.*gpFn)(args...);
}
};
} template <class FpFn>
static auto glfw_input_callback(FpFn fpFn) {
return _impl_glfw_input_callback<FpFn>(fpFn());
} // usage
glfwSetCursorPosCallback(window, glfw_input_callback([] { return &InputCtl::cursor_pos_callback; }));
  1. glfw_input_callback([] { return &InputCtl::cursor_pos_callback; })

    传入一个lambda函数指针, 类型使用 template <class FpFn> FpFn自动定义,函数指针值使用 fpFn承接。

  2. _impl_glfw_input_callback<FpFn>(fpFn());

    fpFn()调用匿名函数,返回 &InputCtl::cursor_pos_callback 成员函数指针。

  3. Variadic Template with member function pointer

template <class, class ...Ts>
static void (*_impl_glfw_input_callback(void (InputCtl::*pFn)(Ts...)))(GLFWwindow *, Ts...)

_impl_glfw_input_callback(void (InputCtl::*pFn)(Ts...)) 使用模板自动承接相应的成员函数指针,不必明确指出函数的返回值,参数等信息。

  1. 函数调用
return [] (GLFWwindow *window, Ts ...args) -> void {
// Game class 的 *this 指针
auto game = (Game *)glfwGetWindowUserPointer(window);
if (game) [[likely]] {
// 成员函数调用
(game->m_inputCtl.*gpFn)(args...);
}
};

注册回调函数的核心无非就是执行回调函数中的代码

X.Refference

  1. Author Vysandeep3
  2. https://isocpp.org/wiki/faq/pointers-to-members
  3. Member Function Pointers and the Fastest Possible C++ Delegates by Don Clugston
  4. How To Bind Lambda To Function Pointer
  5. Practical Design Patterns: Opaque Pointers and Objects in C
  6. Author Vysandeep3
  7. https://www.learncpp.com/cpp-tutorial/introduction-to-pointers/
  8. https://www.tutorialspoint.com/cprogramming/c_variable_arguments.htm
  9. https://jameshfisher.com/2016/11/23/c-varargs/
  10. https://www.tutorialspoint.com/c_standard_library/c_macro_va_start.htm
  11. https://cppinsights.io/
  12. https://adroit-things.com/programming/c-cpp/how-to-bind-lambda-to-function-pointer/
  13. https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
  14. 小彭老师 OPENGL 课程实验源代码

C++ 返回函数指针的函数的更多相关文章

  1. C++ —— 返回数组指针的函数 和 返回指向函数的指针的函数

    返回数组指针的函数 基础知识:数组不能被拷贝,函数不能返回数组,只能返回数组的指针或者引用. 定义一个 返回数组指针的函数 的方法,以 一个接收参数为 含有10个整型元素的数组的引用  和 返回一个含 ...

  2. [C/C++]如何解读返回函数指针的函数声明

    今天在看<深入理解C++11>的时候,看到一段有意思的代码: int (*(*pf())())() { return nullptr; } 我立刻就懵了——从来没有见过这样的函数声明.那么 ...

  3. [笔记]C++声明返回数组指针的函数

    数组指针的声明:type (*name)[size]; 由于数组不能拷贝,所以函数不能返回数组.但是函数可以返回指针和引用,所以函数可以返回数组指针或引用. 和数组指针的声明类似: type (*fu ...

  4. C#委托与C语言函数指针及函数指针数组

    C#委托与C语言函数指针及函数指针数组 在使用C#时总会为委托而感到疑惑,但现在总新温习了一遍C语言后,才真正理解的委托. 其实委托就类似于C/C++里的函数指针,在函数传参时传递的是函数指针,在调用 ...

  5. Delphi 函数指针(函数可以当参数)

    首先学习: 指向非对象(一般的)函数/过程的函数指针 Pascal 中的过程类型与C语言中的函数指针相似,为了统一说法,以下称函数指针.函数指针的声明只需要参数列表:如果是函数,再加个返回值.例如声明 ...

  6. Day8 函数指针做函数参数

    课堂笔记 课程回顾         多态 virtual关键字 纯虚函数 virtual func() = 0;         提前布局vptr指针 面向接口编程 延迟绑定 多态的析构函数的虚函数. ...

  7. typedef void (*Fun) (void) 的理解——函数指针——typedef函数指针

    首先介绍大家比较熟悉的typedef int i;//定义一个整型变量i typedef myInt int: myInt j;//定义一个整型变量j 上面介绍得是我们常用的比较简单的typedef的 ...

  8. C/C++回调方式系列之一 函数指针和函数回调模式

    一.函数指针 1. 函数的定义 return_type function_name(parameter list) { function_body } return_type: 返回值,函数一定有返回 ...

  9. c语言的函数指针和函数指针数组的简单demo

    今天,简单记录一下,函数指针和函数指针数组的使用,废话不多说,直接贴上代码,里面有详细的注释,方便以后查阅. #include <cstdio> #include <Windows. ...

  10. c++入门之函数指针和函数对象

    函数指针可以方便我们调用函数,但采用函数对象,更能体现c++面向对象的程序特性.函数对象的本质:()运算符的重载.我们通过一段代码来感受函数指针和函数对象的使用: int AddFunc(int a, ...

随机推荐

  1. 文盘Rust -- Mutex解决并发写文件乱序问题

    在实际开发过程中,我们可能会遇到并发写文件的场景,如果处理不当很可能出现文件内容乱序问题.下面我们通过一个示例程序描述这一过程并给出解决该问题的方法. use std::{ fs::{self, Fi ...

  2. 【后端面经-数据库】Redis详解——Redis基本概念和特点

    目录 1. Redis基本概念 2. Redis特点 2.1 优点 2.2 缺点 3. Redis的应用场景 面试模拟 参考资料 声明:Redis的相关知识是面试的一大热门知识点,同时也是一个庞大的体 ...

  3. 使用kube-bench检测Kubernetes集群安全

    目录 一.系统环境 二.前言 三.CIS (Center for Internet Security)简介 四.什么是Kube-Bench? 五.使用kube-bench检测不安全的设置 5.1 手动 ...

  4. 论文解读(IW-Fit)《Better Fine-Tuning via Instance Weighting for Text Classification》

    Note:[ wechat:Y466551 | 可加勿骚扰,付费咨询 ] 论文信息 论文标题:Better Fine-Tuning via Instance Weighting for Text Cl ...

  5. 行行AI人才直播第16期:【无界AI首席研究员】刘秋衫《AI创新设计:AIGC赋能设计行业的新思维》

    在这一轮生成式AI浪潮中,设计行业是受波及最为广泛的一个行业.这是设计师们始料未及的事情,至少在此之前,人们认为以设计.艺术为首的创意产业是最难被AI改变的产业之一.而生成式AI的出现,与其说是一次冲 ...

  6. Qt开发思想探幽]QObject、模板继承和多继承

    @ 目录 [Qt开发探幽]QObject.模板继承和多继承 1. QObject为什么不允许模板继承: 2.如果需要使用QObject进行多继承的话,子对象引用的父类链至多只能含有一个QObject ...

  7. 以程序员的视角,介绍如何通过API接口获取淘宝商品数据的方法和步骤,并提供实际代码示例

    ​ 当我们想要获取淘宝商品数据时,可以通过调用淘宝开放平台的API接口来实现.下面是一些步骤和示例代码来帮助你开始. 步骤1:申请开发者账号和应用 在开始之前,你需要在淘宝开放平台上注册一个开发者账号 ...

  8. Hugging News #0904:🤗 登陆 AWS Marketplace

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  9. 文心一言 VS 讯飞星火 VS chatgpt (95)-- 算法导论9.2 4题

    四.用go语言,假设用RANDOMIZED-SELECT 去选择数组 A=(3,2,9,0,7,5,4,8,6,1)的最小元素,给出能够导致 RANDOMIZED-SELECT最坏情况发生的一个划分序 ...

  10. oracle-组合索引字段位置与查询效率之间的关系

    Oracle索引组合字段的位置不同,当查询条件不能覆盖索引时,影响查询效率.查询条件是不是索引字段的第一列影响执行计划,实验验证 实验1:查询条件为组合索引的第一列--创建测试表 create tab ...