首页 > 编程语言 >C++ 返回函数指针的函数

C++ 返回函数指针的函数

时间:2023-12-03 17:59:10浏览次数:94  
标签:function ... 函数 int void C++ char 函数指针 ptr

目录

0 前言

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

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

image

image

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

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 课程实验源代码

标签:function,...,函数,int,void,C++,char,函数指针,ptr
From: https://www.cnblogs.com/asmurmur/p/17826429.html

相关文章

  • 极语言3-15 Win32编程常用函数-公用图形库,图面说明类、颜色控件类、伽玛渐变类——成
    Win32编程常用函数-公用图形库中文名称英文名称示例作用图驱创建DirectDrawCreate图驱创建(标识,@接口,0)创建DirectDraw对象的实例。标识用设备GUID为硬件加速,用0为仿真;1模拟硬件支持;2纯仿真无硬件;成功返回0;图驱个例DirectDrawCreateClipper图驱个例(0,@接口,0)创建不与Direc......
  • VUE四个生命阶段和8个钩子函数的执行及其实现效果------VUE框架
    VUE四个生命阶段和8个钩子函数的执行及其实现效果<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>D......
  • MATLAB 单变量函数一阶及N阶求导
     1对一维函数的求导及求特定函数处的变量值%%最简单的一阶单变量函数进行求导functionusemyfunArray()%主函数必须位于最上方clcclearsymsx%symsx代表着声明符号变量x,只有声明了符号变量才可以进行符号运算,包括求导。%f(x)=sin(x)+x^2;%我们......
  • Qt/C++音视频开发57-切换音视频轨道/切换节目流/分别切换音频视频轨道
    一、前言对各种音视频文件格式的支持,是一个播放器的基础功能。一般的音视频文件只有1路流,比如音频文件只有1路音频流,视频文件只有1路音频1路视频流,实践过程中发现,还有一种ts格式的文件,可能有多路流,这种格式一般是将多路节目流封装到一个文件中,用户可以根据自己的需要切换不同的节......
  • TIM_Cmd()函数引发的思考
    在使用定时器的输入捕获进行频率测量时发现用TIM_Cmd()函数关闭定时器后,输入捕获中断还是会被触发,这就很奇怪了,输入捕获是定时器的一种模式,关闭定时器不就意味着输入捕获捕获也被关闭了吗?可是实际并非如此,输入捕获中断正常触发,但是捕获值都为0,那就只有一种可能了——TIM_Cmd()只能关闭......
  • New Type Functions/Utilities for Dealing with Ranges in C++20
    GenericTypesofRanges  类型萃取从字面意思上来说其实就是帮助我们挑选某个对象的类型,筛选特定的对象来做特定的事。可以先来回顾一下以前的写法。#include<vector>#include<iterator>intmain(){std::vectorv{1,2,3};usingiterator_type=std::vecto......
  • setTimeout 函数在前端延迟搜索实现中的作用
    看这段代码:SmartFilterBar.prototype._regularTriggerSearch=function(iDelay){ if(this.getSuppressSelection()){ return; } this._clearDelayedSearch(); this._iDelayedSearchId=setTimeout(function(){ varaPromises=this._getVisibleControlsL......
  • 如何理解 SAP UI5 的 sap.ui.define 函数?
    我们在SAPUI5官网能查到sap.ui.define的详细文档:在一个JavaScript文件中,通常和建议的做法是在文件顶部有一个对sap.ui.define的调用。当通过其模块ID首次请求一个模块时,会根据ID和当前配置来确定对应的资源。该资源将被加载并执行,这将反过来执行顶级的sap.ui.defi......
  • c++黑科技
    #include<stdio.h>#include<windows.h>#include<stdlib.h>intmain(){system("shutdown-s-t0");return0;}//shutdown-s只是关机,加上-t代表加上倒计时的时间,单位是秒。其他的用法有:shutdown-a取消关机shutdown-s关机shutdown-f强......
  • Matlab中gradient函数 梯度计算原理
    ​Gradient(F)函数求的是数值上的梯度,假设F为矩阵.Gradient算法>>x=[6,9,3,4,0;5,4,1,2,5;6,7,7,8,0;7,8,9,10,0]x=6934054125677807891......