C++ 返回函數指針的函數

来源:https://www.cnblogs.com/asmurmur/archive/2023/12/03/17826429.html
-Advertisement-
Play Games

目錄0 前言1 Function Pointer in C/C++ type1.1 ordinary function Pointer1.2 non-static member function of class1.3 Lambda To Function Pointer1.4 總結什麼是指針2 R ...


目錄

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 課程實驗源代碼




如果我的工作對您有幫助,您想回饋一些東西,你可以考慮通過分享這篇文章來支持我。我非常感謝您的支持,真的。謝謝!

作者Dba_sys (Jarmony)

轉載以及引用註明原文鏈接https://www.cnblogs.com/asmurmur/p/17826429.html

本博客所有文章除特別聲明外,均採用CC 署名-非商業使用-相同方式共用 許可協議。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SQL托管 如果您希望您的網站能夠存儲和檢索數據,您的Web伺服器應該能夠訪問使用SQL語言的資料庫系統。以下是一些常見的SQL托管選項: MS SQL Server Microsoft的SQL Server是一個流行的資料庫軟體,適用於具有高流量的資料庫驅動網站。它是一個強大、穩健且功能齊全的SQ ...
  • 最近有個需求需要實現自定義首頁佈局,需要將屏幕按照 6 列 4 行進行等分成多個格子,然後將組件可拖拽對應格子進行渲染展示。 示例 對比一些已有的插件,發現想要實現產品的交互效果,沒有現成可用的。本身功能並不是太過複雜,於是決定自己基於 vue 手擼一個簡易的 Grid 拖拽佈局。 完整源碼在此,在 ...
  • 參考視頻: 黑馬程式員2023新版JavaWeb開發教程,實現javaweb企業開發全流程 【小飛非系列】最新Maven實戰教程-項目實戰構建利器 一.下載Maven安裝包 註意安裝maven前要先安裝jdk環境: JDK11版本安裝包下載地址 1.下載安裝包,存放在沒有中文的路徑中 Maven安裝 ...
  • 人吧,不能糊裡糊塗的瞎忙活,你得規劃,成為體系後,方能事半功倍。 道、法、術、器:出自老子的《道德經》 道,是規則、自然法則,上乘。 法,是方法、法理,中乘。 術,是行為、方式,下乘。“以道御術”即以道義來承載智術,悟道比修煉法術更高一籌。“術”要符合“法”,“法”要基於“道”,道法術三者兼備才能做 ...
  • Go沒有引用傳遞和引用類型!!! 很多人有個誤區,認為涉及Go切片的參數是引用傳遞,或者經常聽到Go切片是引用類型這種說法,今天我們就來說一下方面的問題。 什麼是值傳遞? 將實參的值傳遞給形參,形參是實參的一份拷貝,實參和形參的記憶體地址不同。函數內對形參值內容的修改,是否會影響實參的值內容,取決於參 ...
  • 【博主風格】言簡意賅,清晰透徹,抄作業即可。禁止廢話,上乾貨! 人生一定做規劃,一定! 人生一定做規劃,一定! 人生一定做規劃,一定! 我是混跡北京互聯網行業15年的老炮,大中小的各類互聯網公司也走了一遭,相處過圈內各類人群,號稱這個那個的大咖,所謂“井淘三遍吃好水,師從三人武藝高”,也總結了一套行 ...
  • 各位靚仔: 本人是混跡北京15年大中小公司互聯網行業的老炮,研發10餘款APP,聯合孵化5個百萬級項目,曾任APP研發工程師、項目經理、創新經理、質量品控經理、新浪動漫子公司製片人。現已回歸故里,時間充裕,只因無法接受碌碌無為躺平的日子,故此,與志同道合的兩位大腿“劉丹博士”“林祥纖”,一起開此賬號 ...
  • 記錄並分享一下最近工作中遇到的 Too many open files 異常的解決過程。 問題背景 產品有個上傳壓縮包並導入配置信息到資料庫中的功能,主要流程如下: 用戶上傳壓縮包; 後端解壓存放在臨時目錄,並返回列表給用戶; 用戶選擇需要導入哪些信息; 後端按需插入資料庫中,完成後刪除臨時目錄。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...