主頁 > 後端開發 > 函式延遲系結的C++實作

函式延遲系結的C++實作

2020-09-14 20:25:42 後端開發

宣告:本博客作者與此博客https://blog.csdn.net/cjw_soledad/article/details/105585806相同

  • 本文代碼需要c++17支持(可自行修改以兼容c++11)

概述

有時候我們會對相同的資料做不同的操作,例如:

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int do_sth(int a, int b, const std::string& function_name) {
	if (function_name == "add") return add(a, b);
	if (function_name == "mul") return mul(a, b);
}

這種做法是可行的,但是當我們還需要添加sub(a, b)div(a, b)等等的多個函式時,每添加一個函式,我們都要在do_sth中添加一個if,很容易出錯,也不符合開閉原則,

另一種實作方法是將每個操作單獨封裝成一個類,然后使用工廠模式創建,這種做法是符合開閉原則的,但是每添加一個函式就要添加一個類未免也太繁瑣了,

理想情況下,倘若有一門語言同時結合了c++, Java,Python,應該這樣添加函式:

// 函式管理類FunctionManager
//     @Register():將函式注冊到這個類中
//     getFunction(): 將已注冊的函式回傳
class FunctionManager;

@Register("add") // 將函式add注冊到FunctionManager,通過字串"add"能夠找到這個函式
int add(int a, int b) { return a + b; }
@Register("mul") // 將函式mul注冊到FunctionManager,通過字串"mul"能夠找到這個函式
int mul(int a, int b) { return a * b; }
int do_sth(int a, int b, const std::string& function_name) {
	// 函式管理類根據function_name回傳一個std::function
	std::function<int(int, int)> Function = FunctionManager.getFunction(function_name);
	return Function(a, b);
}
  • FunctionManager: 管理函式的類
  • @Register("Add"): 將函式指標和函式簽名(能夠唯一標識該函式的字串)添加到FunctionManager
  • FunctionManager.getFunction(): 根據函式簽名回傳函式指標

很明顯,當前的c++不支持@Register,退而求其次我們使用宏進行注冊,最終本文實作的效果是(在最后提供了可運行的完整程式):

// xxx.h
int add(int a, int b);
// xxx.cpp
// 注冊函式,引數為:變數名(ADD),函式簽名("ADD"),函式指標(add)
_REGISTER_FUNCTION(ADD, "ADD", add);
int add(int a, int b) { return a + b; }
// main.cpp
int do_sth(int a, int b, const std::string& func_sig) {
	// FunctionManager是一個單例
	auto p_function_manager = FunctionManager<decltype(add)>::getInstance();
	// 此處回傳已注冊的函式
	auto func = p_function_manager->getFunction(func_sig);
	return 
}

實作

我們將管理函式的類命名為FunctionManager,仔細分析我們的需求,不難發現實際上我們需要根據字串查找注冊的函式物件,

查找的實作

STL中已經有了能夠滿足我們需求的std::map<std::string, FunctionType>容器,因此查找功能已經實作了,需要注意的有:

  • FunctionType需要外部提供,而函式的回傳值、引數都會改變都會影響FunctionType,我們不可能把FunctionType硬編碼到程式中,這時候就需要模板,
  • 由于函式指標不能夠作為函式的回傳值,獲取函式的介面只能回傳std::function,因此map中存盤的也應當是std::function
  • 考慮到程式中僅有一個FunctionManager,將FunctionManager設定為單例更方便(同時也能滿足注冊函式的需求),
template <typename FunctionType>
class FunctionManager {
	// 沒有delete這個指標,考慮到這是demo,忽略這個問題
	inline static FunctionManager<FunctionType>* p_function_manager = nullptr;

	std::map<std::string, FunctionType> m_sig_func_map;

public:
	static FunctionManager<FunctionType>* get_instance() {
		if (!p_function_manager) p_function_manager = new FunctionManager<FunctionType>;
		return p_function_manager;
	}
	
	// 實際上不能使用運算子[],因為當map中sig不存在時會自動創建一個<sig, empty>物件,demo中忽略這個問題
	FunctionType get_function(const std::string& sig) { return m_sig_func_map[sig]; }
};

注冊的實作

注冊實際上就是將函式簽名(字串)和函式(std::function物件)添加到FunctionManager::m_sig_func_map中,只需要添加一個介面:

	// 也可以使用insert,二者間存在一點區別
	void register_function(const std::string& sig, FunctionType function) { m_sig_func_map[sig] = function; }

嘗試使用

到這里我們已經可以使用FunctionManager了:

int add(int a, int b) { return a + b; }
void use() {
	std::function<int(int, int)> a(add);
	FunctionManager<std::function<decltype(add)>>::get_instance()->register_function("add", a);
	auto another_add = FunctionManager<std::function<decltype(add)>>::get_instance()->get_function("add");
	std::cout << another_add(1, 3) << std::endl;
}

我們看到FunctionManager的使用其實是很不方便的:

  • 注冊時需要提供對應函式的std::function物件,實際上我們在使用的時候只希望提供函式的指標
  • 注冊和獲取函式時都需要需要獲取單例

這些冗雜的代碼可以交給單獨的介面進行封裝:

template <typename FunctionPtr>
void register_function(const std::string& function_sig, FunctionPtr function_ptr) {
	auto function_obj = static_cast<std::function<FunctionPtr>>(function_ptr);
	auto p_function_manager = FunctionManager<decltype(function_obj)>::get_instance();
	p_function_manager->register_function(function_sig, function_obj);
}

template <typename FunctionType>
FunctionType get_function(const std::string& function_sig) {
	auto p_function_manager = FunctionManager<FunctionType>::get_instance();
	return p_function_manager->get_function(function_sig);
}

int main(int argc, char* argv[]) {
	register_function<decltype(add)>("add", add); // 只提供指標
	auto another_add = get_function<std::function<int(int, int)>>("add");
	std::cout << another_add(1, 4) << std::endl;
	return 0;
}

相比之下使用起來方便多了,實際上到這里FunctionManager的實作就已經完成了,但是還要讓FunctionManager更好用,以及讓FunctionManager適用于更多型別的函式,下面才是文章的重點

注冊

現有的FunctionManager如果直接使用還會存在一個問題:需要由用戶保證在get_functionregister_function,在demo中這并不是大問題,但是放在大型專案中,get_function在多個檔案中被多次呼叫,需要由用戶保證register_function在所有get_function前執行,這實在是太危險了,穩妥一點的方法是:

void register_all_function() {
	auto p_function_manager = /* get singleton instance */ ;
	p_function_manager->register_function(sig_1, func_1);
	p_function_manager->register_function(sig_2, func_2);
	// ...
}
int main {
	register_all_function();
}

main最開始的時候進行注冊是最安全的做法,但是每添加一個函式,用戶就要在register_all_function中進行注冊,這也違背了開閉原則,

因此我們有了新的需求:main函式執行前完成物件的注冊,有兩種方法能夠在main前執行一段函式:靜態成員變數全域變數需要注意的是二者均需要在cpp檔案中定義,不能夠在頭檔案中,通過register_function為全域變數賦值能夠在main前執行register_function,因此我們還需要為register_function添加一個回傳值:

template <typename FunctionPtr>
bool register_function(const std::string& function_sig, FunctionPtr function_ptr) {
	// ...
	return true;
}

用全域變數注冊函式

通過定義全域變數能夠在main執行前將需要的函式注冊至FunctionManager,為了在使用更方便、代碼可讀性更高,FunctionManager提供了一個宏_REGISTER_FUNCTION,用于封裝注冊函式,其原理如下:

#define _REGISTER_FUNCTION(FunctionSig, FunctionPtr) \
	bool b = register_function<decltype(FunctionPtr)>(FunctionSig, FunctionPtr);

這樣帶來了新的問題:在實際使用時,一個.cpp檔案內通常會有多個型別相同的函式的實作,_REGISTER_FUNCTION將被呼叫多次:

_REGISTER_FUNCTION("ADD", add);
int add(int a, int b) { return a + b; }
_REGISTER_FUNCTION("MUL", mul); // 編譯錯誤,重復定義的變數b
int mul(int a, int b) { return a * b; }

多次呼叫_REGISTER_FUNCTION會導致全域變數b被重復定義,因此需要用戶手動提供不重復的變數名(VariableName)以防止編譯錯誤,最終_REGISTER_FUNCTION的實作如下:

#define _REGISTER_FUNCTION(VariableName, FunctionSig, FunctionPtr) \
	bool Bool##VariableName = register_function<decltype(FunctionPtr)>(FunctionSig, FunctionPtr);

// xxx.h
int add(int, int);
// xxx.cpp
_REGISTER_FUNCTION(ADD, "ADD", add);
int add(int a, int b) { return a + b; }

FunctionManager適配的函式

普通函式

  • 普通函式
float add(float a, float b) { return a + b; }
_REGISTER_FUNCTION(ADD, "ADD", add);

auto new_add = get_function<decltype(add)>("ADD");
  • 命名空間內的函式
namespace FunctionManagerTest {
    float add(float a, float b) { return a + b; }
}
_REGISTER_FUNCTION(ADD, "ADD", FunctionManagerTest::add);

auto new_add = get_function<std::function<decltype(FunctionManagerTest::add)>>("ADD");
  • 模板函式
template<typename T> T addT(T a, T b) { return a + b; }
_REGISTER_FUNCTION(ADD, "ADD", addT<int>);

 auto new_add = get_function<std::function<decltype(addT<int>)>>("ADD");

類內函式

  • 靜態函式
class Real {
public:
    static float add(float a, float b) { return a + b; }
};
_REGISTER_FUNCTION(ADD, "ADD", Real::add);

auto new_add = get_function<std::function<decltype(Real::add)>>("ADD");
  • 模板類的靜態函式
template<typename T>
class Add {
public:
    static float add(T a, T b) { return a + b; }
};
_REGISTER_FUNCTION(ADD, "ADD", Add<int>::add);

auto new_add = get_function<std::function<decltype(Add<int>::add)>>("ADD");

適配成員函式

現在的FunctionManager能夠支持的函式少了很重要的一類:成員函式,因為成員函式在被呼叫時會有一個this指標作為隱式引數,顯然直接通過&Real::add是無法獲得this指標的,這意味著我們需要添加新的介面,

template<typename FunctionPtr, typename ObjectPtr>
bool register_member_function(FunctionPtr func_ptr, ObjectPtr obj_ptr) {
	return true;
}

回看需求,我們希望能夠在FunctionManager中獲取到函式后,能夠直接呼叫;同時FunctionManager中管理的也只是能夠直接呼叫的std::function物件,并未區分成員函式或者非成員函式,現在問題簡化為如何為某一個函式提供一個默認的引數(物件指標),提供后,我們就能像呼叫普通函式一樣呼叫成員函式了,

我們知道std::bind能夠將函式與引數系結,回傳一個Callable物件;該物件能夠使用對應的std::funtion接收;結合std::placeholder還能夠在呼叫回傳的Callable時提供引數:

class Real { public: int add(int a, int b) { return a + b; } }

Real real;
std::function<int(int,int)> binded_add = std::bind(&Real::add, &real, std::placeholders::_1, std::placeholders::_2);
std::cout << binded_add(2, 1) << std::endl;

現在如何提供默認引數的問題解決了,但是另一個問題又出現了:代碼中的std::function<int(int,int)>是硬編碼進去的,肯定不能實裝,我們需要一種能夠自動填充std::function<>內模板引數的方法,在網上找了大半天后,終于有了一種基于模板的解決方案:

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr>
bool register_memeber_function(const std::string& sig, Ret(Struct::* func_ptr)(Args...) const, ObjectPtr obj_ptr) {
	std::function<Ret(Args...)> func = std::bind(func_ptr, obj_ptr);
	auto p_funciton_manager = FunctionManager<decltype(func)>::get_instance();
	p_function_manager->register_function(sig, func);
	return true;
}

class Real { public: int add(int a, int b) { return a + b; } }

Real real;
bool b = register_member_function("ADD", &Real::add, &real);

&Real::add作為引數傳入后能夠自動推匯出Ret, Struct以及可變引數Args;由于我們會系結物件指標,因此我們只需要回傳值Ret,引數Args作為std::function的模板引數,這樣一來std::function的模板引數問題終于解決了,

然而現在的代碼是無法通過編譯的,因為std::bind中沒有添加正確數量的std::placeholder,這個問題的解決需要用到一點元編程,StackOverflow上有人用自定義placeholder以及std::make_integer_sequence的方法來實作引數可變的std::bind

// https://stackoverflow.com/questions/26129933/bind-to-function-with-an-unknown-number-of-arguments-in-c
template<int N>
struct my_placeholder { static my_placeholder ph; };

template<int N>
my_placeholder<N> my_placeholder<N>::ph;

namespace std {
    template<int N>
    struct is_placeholder<::my_placeholder<N>> : std::integral_constant<int, N> { };
}

template<class R, class T, class...Types, class U, int... indices>
std::function<R (Types...)> bind_first(std::function<R (T, Types...)> f, U val, std::integer_sequence<int, indices...> /*seq*/) {
    return std::bind(f, val, my_placeholder<indices+1>::ph...);
}
template<class R, class T, class...Types, class U>
std::function<R (Types...)> bind_first(std::function<R (T, Types...)> f, U val) {
    return bind_first(f, val, std::make_integer_sequence<int, sizeof...(Types)>());
}

這里的核心思想是在模板中傳入長度與引數個數相同的整數序列,并且為每個序列中的整數添加一個placeholder,其實并不需要自己定義一個placeholder,因為std::placeholder的實作是類似的:

// PLACEHOLDER ARGUMENTS
namespace placeholders {
    _INLINE_VAR constexpr _Ph<1> _1{};
    _INLINE_VAR constexpr _Ph<2> _2{};
} // namespace placeholders

結合已有實作以及std::placeholder的解決方案如下:

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr, int... Indices>
std::function<Ret(Args...)> erase_class_info(Ret(Struct::* func_ptr)(Args...), ObjectPtr obj_ptr, std::integer_sequence<int, Indices...>)
{
	std::function<Ret(Args...)> erased_function = std::bind(func_ptr, obj_ptr, std::_Ph<Indices + 1>{}...);
	return erased_function;
}

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr>
bool register_memeber_function(const std::string& sig, Ret(Struct::* func_ptr)(Args...), ObjectPtr obj_ptr) {
	std::function<Ret(Args...)> erased_func = erase_class_info(func_ptr, obj_ptr, std::make_integer_sequence<int, sizeof...(Args)>());
	auto p_funciton_manager = FunctionManager<decltype(erased_func)>::get_instance();
	p_funciton_manager->register_function(sig, erased_func);
	return true;
}

另外,成員函式添加const以后的函式型別是不同的,簡單地添加兩個類似介面可以解決這個問題:

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr, int... Indices>
std::function<Ret(Args...)> erase_class_info(Ret(Struct::* func_ptr)(Args...) const, ObjectPtr obj_ptr, std::integer_sequence<int, Indices...>)
{
	std::function<Ret(Args...)> erased_function = std::bind(func_ptr, obj_ptr, std::_Ph<Indices + 1>{}...);
	return erased_function;
}

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr>
bool register_memeber_function(const std::string& sig, Ret(Struct::* func_ptr)(Args...) const, ObjectPtr obj_ptr) {
	std::function<Ret(Args...)> erased_func = erase_class_info(func_ptr, obj_ptr, std::make_integer_sequence<int, sizeof...(Args)>());
	auto p_funciton_manager = FunctionManager<decltype(erased_func)>::get_instance();
	p_funciton_manager->register_function(sig, erased_func);
	return true;
}

現在已經支持成員函式的注冊了:

class Real {
public:
	int add(int a, int b) const { return a + b; }
	int sub(int a, int b) { return a - b; }
};

Real real;
bool b1 = register_memeber_function("real", &Real::add, &real);
bool b2 = register_memeber_function("REAL", &Real::sub, &real);
auto f1 = get_function<std::function<int(int, int)>>("real");
auto f2 = get_function<std::function<int(int, int)>>("REAL");
std::cout << f1(2, 1) << std::endl;
std::cout << f2(2, 1) << std::endl;

至此FunctionManager實作完成


補充

更簡單的介面

前面提到了使用靜態成員變數也能夠實作注冊功能,用靜態成員變數注冊函式
可以進一步減少用戶需要寫的代碼,FunctionManager希望不需要用戶提供變數名,參考boost宏BOOST_CLASS_EXPORT,其利用模板類和靜態成員變數實作了類的注冊,并且不需要用戶提供變數名,其原理如下:

namespace boost::archive::detail::extra_detail {
    template<> struct init_guid<ClassToRegister> {
        static guid_initializer<ClassToRegister> const& g; //靜態成員g
    }
    static guid_initializer<ClassToRegister> const& g = register_function(); // 定義g,同時注冊類
}

BOOST_CLASS_EXPORT還是不能滿足需求,因為BOOST_CLASS_EXPORT用于注冊類,倘若用于注冊多個相同型別的函式會導致靜態成員g重復定義,而實際注冊函式時同一型別的函式往往會被注冊多次,如:

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
_BOOST_LIKE_REGISTER("ADD", add);
_BOOST_LIKE_REGISTER("MUL", mul); // 靜態成員g重復定義

FunctionManager通過命名空間解決重復定義這一問題,宏_EASY_REGISTER_FUNCTION實作如下:

#define _EASY_REGISTER_FUNCTION(FunctionSig, FunctionPtr) \
    namespace VicentChenSpace { \
        namespace Dummy { \
            namespace FunctionPtr { \
                struct Dummy { \
                    static bool const& b; \
                }; \
            } \
        } \
    } \
    bool const& VicentChenSpace::Dummy::FunctionPtr::Dummy::b = register_function<decltype(::FunctionPtr)>(FunctionSig, ::FunctionPtr); \

雖然_EASY_REGISTER_FUNCTION使用起來更方便,但有以下問題:

  • 需要c++17支持
  • 不支持注冊帶有模板的函式
  • 命名空間相關問題

可運行demo

#include <functional>
#include <map>
#include <iostream>

#define _REGISTER_FUNCTION(VariableName, FunctionSig, FunctionPtr) \
	bool Bool##VariableName = register_function<decltype(FunctionPtr)>(FunctionSig, FunctionPtr);

template <typename FunctionType>
class FunctionManager {
	// 沒有delete這個指標,考慮到這是demo,忽略這個問題
	inline static FunctionManager<FunctionType>* p_function_manager = nullptr;

	std::map<std::string, FunctionType> m_sig_func_map;

public:
	static FunctionManager<FunctionType>* get_instance() {
		if (!p_function_manager) p_function_manager = new FunctionManager<FunctionType>;
		return p_function_manager;
	}

	// 也可以使用insert,二者間存在一點區別
	void register_function(const std::string& sig, FunctionType function) { m_sig_func_map[sig] = function; }
	
	// 實際上不能使用運算子[],因為當map中sig不存在時會自動創建一個<sig, empty>物件,demo中忽略這個問題
	FunctionType get_function(const std::string& sig) { return m_sig_func_map[sig]; }
};

template <typename FunctionPtr>
bool register_function(const std::string& function_sig, FunctionPtr function_ptr) {
	auto function_obj = static_cast<std::function<FunctionPtr>>(function_ptr);
	auto p_function_manager = FunctionManager<decltype(function_obj)>::get_instance();
	p_function_manager->register_function(function_sig, function_obj);
	return true;
}

template <typename FunctionType>
FunctionType get_function(const std::string& function_sig) {
	auto p_function_manager = FunctionManager<FunctionType>::get_instance();
	return p_function_manager->get_function(function_sig);
}

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr, int... Indices>
std::function<Ret(Args...)> erase_class_info(Ret(Struct::* func_ptr)(Args...), ObjectPtr obj_ptr, std::integer_sequence<int, Indices...>)
{
	std::function<Ret(Args...)> erased_function = std::bind(func_ptr, obj_ptr, std::_Ph<Indices + 1>{}...);
	return erased_function;
}

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr>
bool register_memeber_function(const std::string& sig, Ret(Struct::* func_ptr)(Args...), ObjectPtr obj_ptr) {
	std::function<Ret(Args...)> erased_func = erase_class_info(func_ptr, obj_ptr, std::make_integer_sequence<int, sizeof...(Args)>());
	auto p_funciton_manager = FunctionManager<decltype(erased_func)>::get_instance();
	p_funciton_manager->register_function(sig, erased_func);
	return true;
}

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr, int... Indices>
std::function<Ret(Args...)> erase_class_info(Ret(Struct::* func_ptr)(Args...) const, ObjectPtr obj_ptr, std::integer_sequence<int, Indices...>)
{
	std::function<Ret(Args...)> erased_function = std::bind(func_ptr, obj_ptr, std::_Ph<Indices + 1>{}...);
	return erased_function;
}

template <typename Ret, typename Struct, typename ...Args, typename ObjectPtr>
bool register_memeber_function(const std::string& sig, Ret(Struct::* func_ptr)(Args...) const, ObjectPtr obj_ptr) {
	std::function<Ret(Args...)> erased_func = erase_class_info(func_ptr, obj_ptr, std::make_integer_sequence<int, sizeof...(Args)>());
	auto p_funciton_manager = FunctionManager<decltype(erased_func)>::get_instance();
	p_funciton_manager->register_function(sig, erased_func);
	return true;
}

// ----- 普通函式注冊 ----- //
// 普通函式
int add(int a, int b) { return a + b; }
_REGISTER_FUNCTION(ADD, "ADD", add);
int mul(int a, int b) { return a * b; }
_REGISTER_FUNCTION(MUL, "MUL", mul);

// 命名空間內函式
namespace VicentSpace { int add(int a, int b) { return a + b; } }
_REGISTER_FUNCTION(NAMESPACE_ADD, "NAMESPACE_ADD", VicentSpace::add);

// 模板函式
template<typename T> T addT(T a, T b) { return a + b; }
_REGISTER_FUNCTION(TEMPLATE_ADD, "TEMPLATE_ADD", addT<int>);

// ----- 類內函式注冊 ----- //
class Real {
public:
	int add(int a, int b) const { return a + b; }
	int sub(int a, int b) { return a - b; }
	static int mul(int a, int b) { return a * b; }
};
Real real;

template <typename T>
class RealT {
public:
	T addT(T a, T b) const { return a + b; }
	T subT(T a, T b) { return a - b; }
	static T mulT(T a, T b) { return a * b; }
};
RealT<int> real_t;

// 靜態函式
_REGISTER_FUNCTION(STATIC_MUL, "STATIC_MUL", Real::mul);

// 靜態模板函式
_REGISTER_FUNCTION(STATIC_TEMPLATE_MUL, "STATIC_TEMPLATE_MUL", RealT<int>::mulT);

// 成員函式
bool b1 = register_memeber_function("REAL_ADD", &Real::add, &real);
bool b2 = register_memeber_function("REAL_SUB", &Real::sub, &real);

// 模板成員
bool b3 = register_memeber_function("REALT_ADD", &RealT<int>::addT, &real_t);
bool b4 = register_memeber_function("REALT_SUB", &RealT<int>::subT, &real_t);

int main(int argc, char* argv[]) {

	// 普通函式
	auto normal_add = get_function<std::function<decltype(add)>>("ADD");
	auto normal_mul = get_function<std::function<decltype(add)>>("MUL");
	std::cout << "Normal Add 1 + 2 = " << normal_add(1, 2) << std::endl;
	std::cout << "Normal Mul 1 * 2 = " << normal_mul(1, 2) << std::endl;

	// 命名空間內函式
	auto namespace_add = get_function<std::function<decltype(VicentSpace::add)>>("NAMESPACE_ADD");
	std::cout << "Namespace Add 1 + 2 = " << namespace_add(1, 2) << std::endl;

	// 模板函式
	auto template_add = get_function<std::function<int(int, int)>>("TEMPLATE_ADD");
	std::cout << "Template Add 1 + 2 = " << template_add(1, 2) << std::endl;

	// 靜態函式
	auto static_mul = get_function<std::function<int(int, int)>>("STATIC_MUL");
	std::cout << "Static Mul 1 * 2 = " << static_mul(1, 2) << std::endl;
	
	// 靜態模板函式
	auto static_template_mul = get_function<std::function<int(int, int)>>("STATIC_TEMPLATE_MUL");
	std::cout << "Static Template Mul 1 * 2 = " << static_template_mul(1, 2) << std::endl;
	
	// 成員函式
	auto real_add = get_function<std::function<int(int, int)>>("REAL_ADD");
	auto real_sub = get_function<std::function<int(int, int)>>("REAL_SUB");
	std::cout << "Member Add 2 + 1 = " << real_add(2, 1) << std::endl;
	std::cout << "Const Member Sub 2 - 1 = " << real_sub(2, 1) << std::endl;

	// 模板成員
	auto real_t_add = get_function<std::function<int(int, int)>>("REALT_ADD");
	auto real_t_sub = get_function<std::function<int(int, int)>>("REAL_SUB");
	std::cout << "Template Member Add 2 + 1 = " << real_t_add(2, 1) << std::endl;
	std::cout << "Template Const Member Sub 2 - 1 = " << real_t_sub(2, 1) << std::endl;
	
	return 0;
}

參考

  1. StackOverflow - Call a function before main
  2. StackOverflow - std::bind to std::function?
  3. StackOverflow - generic member function pointer as a template parameter
  4. StackOverflow - Bind to function with an unknown number of arguments in C++
  5. cppreference - std::bind

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/38508.html

標籤:C++

上一篇:C++值多型:傳統多型與型別擦除之間

下一篇:Code::Blocks20.03 編譯報錯

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more