假設我正在嘗試為多種訊息型別撰寫多個處理程式。
enum MESSAGE_TYPE { TYPE_ZERO, TYPE_ONE, TYPE_TWO, TYPE_THREE, TYPE_FOUR };
一種解決方案可能是
void handler_for_type_one(...){ ... }
void handler_for_type_two(...){ ... }
...
switch(message_type){
case TYPE_ONE: handler_for_type_one(); break;
case TYPE_TWO: handler_for_type_two(); break;
...
是的,那會很好。但現在我想添加包含每個處理程式的日志記錄。讓我們在處理函式的開頭/結尾說一個簡單printf的(之前和之后也可以)。
所以也許我這樣做:
template<MESSAGE_TYPE>
void handler() {
std::printf("[default]");
}
template<> void handler<TYPE_ONE>() {
std::printf("[one]");
}
template<> void handler<TYPE_TWO>() {
std::printf("[two]");
}
template<> void handler<TYPE_THREE>() {
std::printf("[three]");
}
int main()
{
std::printf("== COMPILE-TIME DISPATCH ==\n");
handler<TYPE_ZERO>();
handler<TYPE_ONE>();
handler<TYPE_TWO>();
handler<TYPE_THREE>();
handler<TYPE_FOUR>();
}
它按我的預期作業:
== 編譯時調度 == [默認][一][二][三][默認]
當訊息型別在編譯時已知時,這很好用。我什至不需要那個丑陋的東西switch。但是在測驗之外我不會知道訊息型別,即使我知道,wrap_handler(用于日志記錄)“擦除”它,需要我使用開關“map”。
void wrap_handler(MESSAGE_TYPE mt) {
std::printf("(before) ");
switch (mt) {
case TYPE_ZERO: handler<TYPE_ZERO>(); break;
case TYPE_ONE: handler<TYPE_ONE>(); break;
case TYPE_TWO: handler<TYPE_TWO>(); break;
case TYPE_THREE: handler<TYPE_THREE>(); break;
//case TYPE_FOUR: handler<TYPE_FOUR>(); break; // Showing "undefined" path
default: std::printf("(undefined)");
}
std::printf(" (after)\n");
}
int main()
{
std::printf("== RUNTIME DISPATCH ==\n");
wrap_handler(TYPE_ZERO);
wrap_handler(TYPE_ONE);
wrap_handler(TYPE_TWO);
wrap_handler(TYPE_THREE);
wrap_handler(TYPE_FOUR);
}
== 運行時調度 == (之前) [默認] (之后) (之前) [一] (之后) (前)【二】(后) (前)【三】(后) (之前) (未定義) (之后)
我對解決方案的“目標”是:
- 讓列舉值盡可能接近處理程式定義——我上面展示的模板專業化似乎是我在這方面能做的最好的事情,但我不知道。
- 添加訊息型別/處理程式時,我希望盡可能保持更改本地/緊密。(基本上,我正在尋找擺脫該開關的任何方法)。
- 如果我確實需要一個開關或映射等,因為它離新的處理程式很遠,我想要一種在編譯時判斷是否存在沒有相應開關情況的訊息型別(列舉值)的方法。(也許使開關成為地圖/陣列?不確定是否可以在編譯時獲得初始化地圖的大小。)
- 最小化樣板
另一個似乎很明顯的解決方案是在不同的子類中重寫的虛擬方法,每個訊息型別都有一個,但似乎沒有辦法將訊息型別(列舉值)“系結”到特定的實作,就像上面的模板專業化。
只是為了完善它,這可以用(其他語言)裝飾器完美地完成:
@handles(MESSAGE_TYPE.TYPE_ZERO)
def handler(...):
...
有任何想法嗎?
uj5u.com熱心網友回復:
我擺脫手動 switch 陳述句的一種方法是使用模板遞回,如下所示。首先,我們創建一個列舉類的整數序列,如下所示:
enum MESSAGE_TYPE { TYPE_ZERO, TYPE_ONE, TYPE_TWO, TYPE_THREE, TYPE_FOUR };
using message_types = std::integer_sequence<MESSAGE_TYPE, TYPE_ZERO, TYPE_ONE, TYPE_TWO, TYPE_THREE, TYPE_FOUR>;
其次,讓我們稍微改變一下處理程式,讓它成為一個帶有靜態函式的類:
template <MESSAGE_TYPE M>
struct Handler
{
// replace with this whatever your handler needs to do
static void handle(){std::cout << (int)M << std::endl;}
};
// specialise as required
template <>
struct Handler<MESSAGE_TYPE::TYPE_FOUR>
{
static void handle(){std::cout << "This is my last message type" << std::endl;}
};
現在,有了這些,我們可以輕松地使用模板遞回來創建通用開關映射:
template <class Sequence>
struct ct_map;
// specialisation to end recusion
template <class T, T Head>
struct ct_map<std::integer_sequence<T, Head>>
{
template <template <T> class F>
static void call(T t)
{
return F<Head>::handle();
}
};
// recursion
template <class T, T Head, T... Tail>
struct ct_map<std::integer_sequence<T, Head, Tail...>>
{
template <template <T> class F>
static void call(T t)
{
if(t == Head) return F<Head>::handle();
else return ct_map<std::integer_sequence<T, Tail...>>::template call<F>(t);
}
};
并使用如下:
int main()
{
ct_map<message_types>::call<Handler>(MESSAGE_TYPE::TYPE_ZERO);
ct_map<message_types>::call<Handler>(MESSAGE_TYPE::TYPE_THREE);
ct_map<message_types>::call<Handler>(MESSAGE_TYPE::TYPE_FOUR);
}
如果現在,你想創建你的 wraphandler,你可以這樣做:
template <MESSAGE_TYPE M>
struct WrapHandler
{
static void handle()
{
std::cout << "Before" << std::endl;
Handler<M>::handle();
std::cout << "After" << std::endl;
}
};
int main()
{
ct_map<message_types>::call<WrapHandler>(MESSAGE_TYPE::TYPE_THREE);
}
現場代碼在這里
uj5u.com熱心網友回復:
我理解它的方式,一個函式指標可能是你需要的。
從您的示例開始,代碼將如下所示:
template<MESSAGE_TYPE>
void handler() {
std::printf("[default]");
}
template<> void handler<TYPE_ONE>() {
std::printf("[one]");
}
template<> void handler<TYPE_TWO>() {
std::printf("[two]");
}
template<> void handler<TYPE_THREE>() {
std::printf("[three]");
}
void wrap_handler(void (*handler)()) {
std::printf("(before) ");
if (!handler)
std::printf("(undefined)");
else
handler();
std::printf(" (after)\n");
}
int main()
{
std::printf("== COMPILE-TIME DISPATCH ==\n");
handler<TYPE_ZERO>();
handler<TYPE_ONE>();
handler<TYPE_TWO>();
handler<TYPE_THREE>();
handler<TYPE_FOUR>();
std::printf("\n\n");
std::printf("== RUNTIME DISPATCH ==\n");
wrap_handler(TYPE_ZERO);
wrap_handler(TYPE_ONE);
wrap_handler(TYPE_TWO);
wrap_handler(TYPE_THREE);
wrap_handler(TYPE_FOUR);
}
函式指標反映了函式的原型(意味著所有呼叫都需要兼容)。為了傳遞引數,函式將更改為:
void wrap_handler(void (*handler)(ArgumentType), const ArgumentType &arg) {
std::printf("(before) ");
if (!handler)
std::printf("(undefined)");
else
handler(arg);
std::printf(" (after)\n");
}
解決此問題的一種方法是使用std::function(C 11)。
void wrap_handler(std::function<> handler) {
std::printf("(before) ");
if (!handler)
std::printf("(undefined)");
else
handler();
std::printf(" (after)\n");
}
可能的呼叫方法包括:
wrap_handler(&functionWithoutArguments);
wrap_handler(std::bind(functionWithArgument, someArgument);
wrap_handler([=](){ LambdaCode; });
etc.
uj5u.com熱心網友回復:
這是所有接收訊息或事件的應用程式的常見問題。但是,在 C 中,開關或某種處理程式表是您能做的最好的事情。原因是列舉的值僅存在于運行時,因此您無法在編譯時做出該決定。其他語言,如 Python,可以提供您正在尋找的解決方案,因為它們是解釋型語言,因此編譯時和運行時是相同的。
Boost asio 是如何隱藏開關的一個很好的例子,但我的經驗是隱藏它并沒有你想象的那么好。當您需要除錯代碼或其他人必須找到屬于某個事件的處理程式時,或者以某種方式,您必須檢查處理程式是否已注冊,您需要知道開關在哪里,在那里放置一個斷點,或記錄傳入的訊息。這在像 asio 這樣的系統中要困難得多。
uj5u.com熱心網友回復:
C 需要在編譯時計算出確切的函式簽名。這確實包括確定模板引數。無論您是為此創建類似地圖的資料結構還是將其保留為switch. 如果您只是擔心在switch或 樣板代碼中不小心遺漏了一些列舉常量,那么這可能是讓前處理器參與其中的時候了。
#ifdef MESSAGE_TYPES
# error macro name conflict for MESSAGE_TYPES may result in errors
#endif
// x is a function-like macro that takes 1 parameter (2, if you want the constants to assigned a specific value)
#define MESSAGE_TYPES(x) \
x(TYPE_ZERO) \
x(TYPE_ONE) \
x(TYPE_TWO) \
x(TYPE_THREE) \
x(TYPE_FOUR)
#ifdef MESSAGE_TYPE_ENUM_CONSTANT
# error macro name conflict for MESSAGE_TYPE_ENUM_CONSTANT may result in errors
#endif
#define MESSAGE_TYPE_ENUM_CONSTANT(c) c,
enum MESSAGE_TYPE { MESSAGE_TYPES(MESSAGE_TYPE_ENUM_CONSTANT) };
#undef MESSAGE_TYPE_ENUM_CONSTANT
template<MESSAGE_TYPE>
void handler() {
std::printf("[default]");
}
template<> void handler<TYPE_ONE>() {
std::printf("[one]");
}
template<> void handler<TYPE_TWO>() {
std::printf("[two]");
}
template<> void handler<TYPE_THREE>() {
std::printf("[three]");
}
void wrap_handler(MESSAGE_TYPE mt) {
std::printf("(before) ");
#ifdef HANDLER_CALL_SWITCH_CASE
# error macro name conflict for HANDLER_CALL_SWITCH_CASE may result in errors
#endif
#define HANDLER_CALL_SWITCH_CASE(c) case c: handler<c>(); break;
switch (mt) {
MESSAGE_TYPES(HANDLER_CALL_SWITCH_CASE);
default:
std::printf("(undefined)");
break;
}
#undef HANDLER_CALL_SWITCH_CASE
std::printf(" (after)\n");
}
#undef MESSAGE_TYPES
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/515159.html
標籤:C 模板动态调度
