运行时从外部读取一个 16 进制数字,然后调用对应的函数,比如读取到1F3
,那么就调用函数foo_1f3
,函数参数也是有编号的,规律是这样:
void foo_1f0(myclass_1f0& val);
void foo_1f1(myclass_1f1& val);
void foo_1f2(myclass_1f2& val);
之前 C#是用的反射,很容易实现。到 C++这不知道怎么搞比较优雅,目前有上百个 if/else 去判断然后调用。
C++这边可以用到 C++20 ,不知道有什么酷的解决方法?
1
lichao 2 天前 1
std::map
|
2
liuguangxuan 2 天前 1
模板。
|
3
blu10ph 2 天前 2
你是否在找:工厂模式/策略模式?
感觉你描述的还比较清晰,把你发的这段内容交给 AI,一会就能出来代码~ |
4
yuzii 2 天前 1
std::vector<std::function<void(int)>> foos;
|
7
aqtata OP @liuguangxuan 展开讲讲。。
|
8
YakumoZi 2 天前 1
@liuguangxuan 模板是编译时计算,他这个要求运行时读取,应该做不到吧?
|
9
zwy100e72 2 天前 1
对于分发用的标志 `1fx` 比较集中的情况,可以直接用 `std::vector<std::function<>>` 然后用数组下标做 key ;对于 key 比较稀疏的情况,可以用 `std::map<int, std::function<>>` 或者 `std::unordered_map`
注册要写几百行这种估计跑不掉的,目前 c++ 还没有正式支持反射;不过可以用部分编译器支持的 `[[constructor]]` 语法,在启动阶段实现自动注册。例如有某 `Dispatcher::register(int, std::function<>)` 方法,部分编译器可以实现: ```c++ [[gnu::constructor]] reg_handler() { Dispatcher::register(0x1f3, foo_1f3); } ``` |
10
fyxtc 2 天前 7
对于无反射的静态编译型语言,这种情况 if 和 switch 是最清晰的,因为阅读代码的人只要看三个 if 就知道这坨是干什么的,也非常容易定位和修改,用其他自以为“优雅”的解决方案很大概率只是满足了自己苦了他人。
|
11
Metatron7 2 天前 1
不想写 ifelse 就 LLM 生成得了
|
12
zwy100e72 2 天前 1
写了一个超省略的 demo ,供参考 https://godbolt.org/z/ox7K6sM1e
|
13
leonshaw 2 天前 via Android 2
直接 switch ,case 用宏包一下,再用工具生成。
强行搞可以把函数导出,运行时查符号。 |
14
guanzhangzhang 2 天前
|
15
geelaw 2 天前 2
wow 这个问题完全 under-specified 。第一个问题:你知道要调用什么函数了,可你怎么制造不同类型的参数传入之?
但楼主不应该尝试回答我的第一个问题,而是应该直接说自己实际上要解决的问题,而不是来问自己觉得可行的一半解决方法的另一半。 |
16
chashao 1 天前 1
额,感觉还得用脚本批量生成函数注册的逻辑。。
```c++ void foo_1f0(int& value) { std::cout << "test!!" << std::endl; } // 这块代码得脚本生成 std::unordered_map<std::string, std::function<void(int&)>> GFunctions = { { "foo_1f0", foo_1f0 } }; int main() { int inParam = 0x1f0; std::stringstream ss; ss << std::hex<<inParam; std::string funcName = "foo_" + ss.str(); int param = 1; GFunctions[funcName](param); } ``` |
17
c3de3f21 1 天前
这样写是最好的
|
18
sapphire 1 天前 1
这种情况适宜从整体项目 build 的角度考量,有工程上的设计和考量。如果语言本身没有反射机制,一般用脚本或其他自定义工具,在 Pre-build 阶段生成函数的桩,同时对参数和调用场景做必要的安全检查。这样对于函数实现代码,只要做好必要的标注,就和 C#没什么区别了。
|
19
MoYi123 1 天前 1
想要酷可以参考 std::visit 的做法. 编译期生成一个 Invoke_array, 下面的例子是从运行期的 int 转特定类型到 lambda 中的例子. 稍微改改就能用于你的需求.
using namespace std; constexpr std::array cached_type_ints = {1}; struct void_ptr { int type; void *ptr; }; template <int T> struct Int2Type; template <> struct Int2Type<1> { using type = int; }; template <typename Func, typename> class Visitor; template <typename Func, std::size_t... pos> class Visitor<Func, std::index_sequence<pos...>> { public: using return_type = std::invoke_result_t<Func, Int2Type<cached_type_ints[0]>::type *>; using fn_type = return_type (*)(Func &&, void *); template <int16_t type_int> static auto func_wrapper(Func &&func, void *ptr) -> return_type { using real_type = typename Int2Type<type_int>::type; return func(static_cast<real_type *>(ptr)); } static auto visit(Func &&func, const void_ptr &item) -> return_type { constexpr static std::array<fn_type, cached_type_ints.size()> invoke_map = { func_wrapper<cached_type_ints[pos]>...}; size_t idx = std::ranges::lower_bound(cached_type_ints.begin(), cached_type_ints.end(), item.type) - cached_type_ints.begin(); if (idx >= invoke_map.size() or cached_type_ints[idx] != item.type) [[unlikely]] { throw std::bad_variant_access(); } return invoke_map[idx](std::forward<Func>(func), item.ptr); } }; template <typename Func> auto visit(Func &&func, const void_ptr &item) { using visitor = Visitor<decltype(func), std::make_index_sequence<cached_type_ints.size()>>; return visitor::visit(std::forward<Func>(func), item); } inline auto usage() { auto item = void_ptr{.ptr = new int(1), .type = 1}; visit( [](auto *ptr) { print(*ptr); delete ptr; }, item); } |
20
securityCoding 1 天前 1
查表 map<key,func>
|
21
HtPM 1 天前 1
宏定义
|
22
qq135449773 1 天前 1
这种需求本来难以简化的,Map<key, func>已经是不错的选择了。
再想继续想办法简化我觉得只能增加后期维护负担。 |
23
nuk 1 天前 1
符号得和得和字符串对上,简单点就是 dlsym ,复杂一点用 linker set ,自己加 map 也可以,就是没那么优雅了。
|
24
DLOG 1 天前 1
请问 您是找 “宏” 的使用方法么
|
25
oneisall8955 1 天前 1
反射有没有?
|
26
shadowyue 1 天前 1
只是平级的 if else 没必要专门消除吧,看起来也是很清晰的。
你要 if else 嵌套很多层,去消除更有意义一点。 |
27
defaw 1 天前
用 map ,key 是数字,value 是函数指针,直接写死在 map 的初始化里
|
31
levelworm 1 天前 via Android
函数指针数组就行了。直接写死。然后直接调用数组成员。
|
32
sampeng 1 天前
查表法简单优雅。比冲击波墙吧。。。
|
33
seanwhy 1 天前 via iPhone 1
c++有个 rttr 的反射开源库,github 上可以搜下
|
34
exonuclease 1 天前
代码生成?
|
35
jcharr 1 天前
```c
|
36
jcharr 1 天前
```c
#define GET_HEX_REGITER(HEX) \ static __always_inline void ctx_##HEX(myclass_1##HEX& val) { \ /*todo*/ \ } GET_HEX_REGITER(a) ``` 这是用宏来写的 感觉应该能满足吧 能不用映射就不用映射 |
37
min 1 天前
这个问题你找不到大模型可以问吗?
|
38
netabare 1 天前 via iPhone
用表驱动和高阶函数会比较好
|
39
nicebird 1 天前
宏定义吧
|
40
nicebird 1 天前
另外现在直接用 copilot 生成就行了,也不用写啥代码
|
41
minami 1 天前
正解是 libffi
|
42
chandlerbing9317 1 天前
分支比较多改成表驱动就好了
|
43
ETiV 1 天前 via iPhone
都是 16 进制范围内就用偏移做呗
|
44
LuJyKa 1 天前
把这堆函数导出成 Dll ,然后用 GetProcAddress 直接传字符串得到函数地址,然后把参数地址传进去。
|
45
vituralfuture 1 天前 via Android
写个脚本,在构建阶段生成文件参与编译
|
46
liuidetmks 1 天前 via iPhone
@jcharr 要 runtime 吧
|
47
FrankFang128 1 天前
选中代码,让 AI 消除 if else
|
48
198plus 1 天前
建议代码生成,用“写代码的代码”维护代码,我记得 rust 编译器里面就有这种东西,用 python 脚本生成 rust 代码
|
49
bluearc 1 天前 1
用模板就可以解决,
``` #include <iostream> #include <string> #include <sstream> #include <stdexcept> #include <cstdint> template<std::uint32_t N> struct myclass { void print() const { throw std::logic_error("Error: myclass<" + std::to_string(N) + "> is not specialized."); } }; // 特化模板(如果需要为某些编号提供特定行为) template<> struct myclass<0x1f0> { void print() const { std::cout << "Specialized myclass<1f0>\n"; } }; template<> struct myclass<0x2f0> { void print() const { std::cout << "Specialized myclass<2f0>\n"; } }; // 通用模板函数 template<std::uint32_t N> void foo(myclass<N>& obj) { std::cout << "Called foo<" << std::hex << N << ">\n"; obj.print(); // 调用特化模板的成员函数(如果有) } // 通用函数:根据运行时输入调用特定模板 void invoke_function(std::string_view hex_input) { // 将字符串解析为 16 进制数值 std::uint32_t num; std::stringstream ss; ss << std::hex << hex_input; ss >> num; myclass<num> obj; foo(obj); } // 主程序 int main() { try { invoke_function("1f0"); // 调用 foo<1f0> invoke_function("2f0"); // 调用 foo<2f0> invoke_function("3f0"); // 抛出异常 } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << '\n'; } return 0; } ``` 以后要添加新的,直接写个对应的特化模板就完事 |
51
chendl111 1 天前
优雅没有任何意义
|
52
ysc3839 1 天前 via Android
@abc612008 用 stringstream 解析确实可能是 GPT 写的,但是直接传参(非运行时,代码里写的是字符串字面量)再转成常量是可行的,constexpr 就可以。
|
53
woniu7 1 天前
优雅没有任何意义
|
54
ysc3839 1 天前 via Android
|
55
mayli 1 天前
我觉得你最好还是给个最小化的例子说下参数怎么不同,不然没法准备参数…
|
56
ysc3839 20 小时 46 分钟前 via Android
@bluearc @abc612008
不对,模板可以解决,需要手动把所有可能的取值写出来,例如: test<0x1f0, 0x1f1, 0x1f2>(i); 如果能接受非标准扩展的话,有种办法可以把取值加到宏列表里: #include <cstdio> #include <cstdlib> template<int N> void func(); template<typename T = void> void test(int n) { puts("test() failed!"); } template<int N, int... ints> void test(int n) { printf("test<%i>(%i)\n", N, n); if (n == N) { func<N>(); } else { test<ints...>(n); } } #define PUSHVAL _Pragma("push_macro(\"VALUES\")") #define POPVAL _Pragma("pop_macro(\"VALUES\")") #define VALUES 2 template<> void func<2>() { puts("func<2>()"); } PUSHVAL #undef VALUES #define VALUES POPVAL VALUES, 3 template<> void func<3>() { puts("func<3>()"); } PUSHVAL #undef VALUES #define VALUES POPVAL VALUES, 5 template<> void func<5>() { puts("func<5>()"); } PUSHVAL #undef VALUES #define VALUES POPVAL VALUES, 7 template<> void func<7>() { puts("func<7>()"); } int main(int argc, char** argv) { if (argc != 2) { printf("usage: %s <int>\n", argv[0]); return 1; } int n = (int)strtoul(argv[1], nullptr, 0); printf("n = %i\n", n); test<VALUES>(n); } 上述方法在 gcc 中有效,msvc 无效。 |
57
bluearc 14 小时 9 分钟前
@ysc3839 #56 我的,让 chatgpt 生成后感觉差不多就发了,上班摸鱼修改了下:
``` #include <cstdint> #include <functional> #include <iostream> #include <map> #include <memory> #include <sstream> #include <stdexcept> #include <string> class BaseClass { public: virtual ~BaseClass() = default; virtual void print() = 0; }; std::map<uint32_t, BaseClass *> reg; template <std::uint32_t N> struct myclass : public BaseClass { void print() override { throw std::logic_error("Error: myclass<" + std::to_string(N) + "> is not specialized."); } }; template <uint32_t N> void register_classes() { reg[N] = new myclass<N>(); if constexpr (N > 0) { register_classes<N - 1>(); } } template <> struct myclass<0x1ff> : public BaseClass { void print() override { std::cout << "Specialized myclass<1f0>\n"; } }; template <> struct myclass<0x1fa> : public BaseClass { void print() override { std::cout << "Specialized myclass<2f0>\n"; } }; // 通用模板函数 void foo(BaseClass *obj) { obj->print(); // 调用特化模板的成员函数(如果有) } // 通用函数:根据运行时输入调用特定模板 void invoke_function(std::string_view hex_input) { // 将字符串解析为 16 进制数值 std::uint32_t num; std::stringstream ss; ss << std::hex << hex_input; ss >> num; foo(reg[num]); } // 主程序 int main() { register_classes<0xfff>(); try { invoke_function("1ff"); invoke_function("1fa"); invoke_function("3f0"); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << '\n'; } for (auto &pair : reg) { delete pair.second; } return 0; } ``` 还是写特化模板就行,编译时给编译器传一个参数:-ftemplate-depth=4096 ; |
58
Nimrod 12 小时 48 分钟前
运行时值到类型的分发,具体的,这里就是`N` 到 `void foo<N>(myclass<N>&)`。
这里存在一个问题需要楼主表达清楚,对应类型的参数是如何构造出来的。 这里为作简化`void foo<N>()` 核心逻辑是做一次`int V`到`std::integral_constant<int, V>`的映射,再用 lambda 包装一下原本的`foo<N>`使得能用上推断出来的类型。 ```cpp #define DISPATCH(VALUE) \ case VALUE: \ return f(std::integral_constant<int, VALUE>{}); // trampline function template <typename F> auto dispatcher(F&& f, int value) { switch(value) { DISPATCH(0x1f0) DISPATCH(0x1f1) DISPATCH(0x1f2) default: throw std::runtime_error("Unregistered value"); } } void foo_wrapper(int num) { dispatcher([](auto type){ constexpr auto v = decltype(type)::value; foo<v>(); }, num); } ``` 这里,`dispatcher`是可以完全可以复用的。 [Demo]( https://godbolt.org/z/5TdevE4We) 剩下的就是手动`DISPATCH(N)`来注册你需要的值,也可以使用 BOOST_PP_REPEAT 来生成代码。 [Demo]( https://godbolt.org/z/1893bzEs8) 运行时的类型分发可以参考我的这篇博客,https://nimrod.blog/posts/cpp-elegant-ways-to-map-runtime-values-to-types |
59
abc612008 9 小时 41 分钟前
@ysc3839 #56 你这个调用一次 func 的时间是 O(n)的吗..难道要从 0 开始试
``` ❯ ./a.out 7 n = 7 test<2>(7) test<3>(7) test<5>(7) test<7>(7) func<7>() ``` |
61
abc612008 9 小时 28 分钟前
@bluearc #57 都用上 map 了那不如这样:
``` #include <iostream> #include <functional> constexpr int maxN = 100; class Foo { public: static void invoke(int i) { _mapping[i](i); } private: static std::function<void(int)> _mapping[maxN]; template <int N> struct FooHelper { FooHelper() { _mapping[N] = [](int n) { std::cout << "Foo<" << N << "> (" << n << ")" << std::endl; }; } }; template <int N> struct Initer : Initer<N-1> { FooHelper<N> _foo; }; static Initer<maxN> _initer; }; template<> struct Foo::Initer<0> {}; std::function<void(int)> Foo::_mapping[maxN]; Foo::Initer<maxN> Foo::_initer; int main(){ int n; std::cin>>n; Foo::invoke(n); } ``` 虽然感觉也挺丑的 |
62
ysc3839 8 小时 17 分钟前
@bluearc @abc612008
去请教了一下群友,成功通过 Stateful Template Meta Programming (STMP) 实现了仅在模板特化时声明一次取值,后续自动根据所有可能的取值生成代码: https://godbolt.org/z/GqY7ozrh7 代码基于 https://ykiko.me/zh-cn/articles/646812253/ 该文章修改,未进行优化,仅仅是展示可行性。 这代码说多不多、说少不少,感觉并不是非常优雅,看情况选择是否使用吧。 gcc 及 msvc 都可用,gcc 会报 warning ,msvc 不会。 |