如何避免对编译后无法访问的正在运行的代码部分进行运行时检查?
How to avoid run-time checks for running parts of code that become unreachable after compilation?
我的程序从用户那里获取几个布尔变量,之后它们的值不会改变。每个布尔变量启用一部分代码。像这样:
#include <iostream>
void callback_function(bool task_1, bool task_2, bool task_3) {
if (task_1) {
std::cout << "Running task 1" << std::endl;
}
if (task_2) {
std::cout << "Running task 2" << std::endl;
}
if (task_3) {
std::cout << "Running task 3" << std::endl;
}
}
int main() {
bool task_1 = true;
bool task_2 = false;
bool task_3 = true;
while (true) {
callback_function(task_1, task_2, task_3);
}
return 0;
}
现在我的问题是,由于布尔变量在每次程序调用callback_function()
时都是固定的,有没有办法避免回调函数中的if
语句?
这是避免运行时检查的一种方法(为布尔变量的所有排列实现回调函数---下面只显示两种情况):
#include <functional>
#include <iostream>
void callback_function_for_tasks_1_2_3() {
std::cout << "Running task 1" << std::endl;
std::cout << "Running task 2" << std::endl;
std::cout << "Running task 3" << std::endl;
}
void callback_function_for_tasks_1_3() {
std::cout << "Running task 1" << std::endl;
std::cout << "Running task 3" << std::endl;
}
int main() {
bool task_1 = true;
bool task_2 = false;
bool task_3 = true;
std::function<void()> callback_function;
if (task_1 && task_2 && task_3) {
callback_function = callback_function_for_tasks_1_2_3;
} else if (task_1 && !task_2 && task_3) {
callback_function = callback_function_for_tasks_1_3;
}
while (true) {
callback_function();
}
return 0;
}
问题是,如果有n
布尔变量,我必须实现2^n
不同的回调函数。有没有更好的方法来实现这一目标?
确保在编译时计算 if 语句
C++17 引入了if constexpr
,它正是这样做的:
template<bool task_1, bool task_2, bool task_3>
void callback_function() {
if constexpr (task_1) {
std::cout << "Running task 1" << std::endl;
}
if constexpr (task_2) {
std::cout << "Running task 2" << std::endl;
}
if constexpr (task_3) {
std::cout << "Running task 3" << std::endl;
}
}
如果启用了优化,则无需if constexpr
。即使您使用常规if
而不是if constexpr
,因为布尔值现在是模板化的,编译器将能够完全消除if
语句,只运行任务。如果你看一下这里生成的程序集,你会发现即使在-O1
,在任何callback
函数中都没有if语句。
我们现在可以直接将callback_function
用作函数指针,避免function<void()>
:
int main() {
using callback_t = void(*)();
callback_t func = callback_function<true, false, true>;
// Do stuff with func
}
我们还可以通过将它们分配给 constexpr 变量来命名bool
s:
int main() {
using callback_t = void(*)();
constexpr bool do_task1 = true;
constexpr bool do_task2 = false;
constexpr bool do_task3 = true;
callback_t func = callback_function<do_task1, do_task2, do_task3>;
// Do stuff with func
}
自动创建所有可能的回调函数的查找表
您提到了在运行时在不同的回调函数之间进行选择。我们可以通过查找表轻松做到这一点,并且我们可以使用模板自动创建所有可能的回调函数的查找表。
第一步是从特定索引获取回调函数:
// void(*)() is ugly to type, so I alias it
using callback_t = void(*)();
// Unpacks the bits
template<size_t index>
constexpr auto getCallbackFromIndex() -> callback_t
{
constexpr bool do_task1 = (index & 4) != 0;
constexpr bool do_task2 = (index & 2) != 0;
constexpr bool do_task3 = (index & 1) != 0;
return callback_function<do_task1, do_task2, do_task3>;
}
一旦我们可以做到这一点,我们就可以编写一个函数来从一堆索引创建一个查找表。我们的查找表将只是一个std::array
。
// Create a std::array based on a list of flags
// See https://en.cppreference.com/w/cpp/utility/integer_sequence
// For more information
template<size_t... Indexes>
constexpr auto getVersionLookup(std::index_sequence<Indexes...>)
-> std::array<callback_t, sizeof...(Indexes)>
{
return {getCallbackFromIndex<Indexes>()...};
}
// Makes a lookup table containing all 8 possible callback functions
constexpr auto callbackLookupTable =
getVersionLookup(std::make_index_sequence<8>());
在这里,callbackLookupTable
包含所有 8 个可能的回调函数,其中callbackLookupTable[i]
扩展i
位以获取回调。例如,如果i == 6
,则i
的位以二进制110
,因此
callbackLookupTable[6]
callback_function<true, true, false>
在运行时使用查找表
使用查找表非常简单。我们可以通过位移从一堆bool
中获取索引:
callback_t getCallbackBasedOnTasks(bool task1, bool task2, bool task3) {
// Get the index based on bit shifting
int index = ((int)task1 << 2) + ((int)task2 << 1) + ((int)task3);
// return the correct callback
return callbackLookupTable[index];
}
演示如何在任务中读取的示例
我们现在可以在运行时获取bool
,只需调用getCallbackBasedOnTasks
即可获得正确的回调
int main() {
bool t1, t2, t3;
// Read in bools
std::cin >> t1 >> t2 >> t3;
// Get the callback
callback_t func = getCallbackBasedOnTasks(t1, t2, t3);
// Invoke the callback
func();
}
保持代码原样。
与写入 std::out 相比,"if"的执行时间几乎为零,所以你什么都不争论。好吧,除非您花一些时间按原样测量执行时间,并根据三个常量的值删除 if,并发现存在真正的差异。
最多,您可能会使函数内联或静态,并且编译器可能会意识到在启用优化时参数始终相同。(我的编译器会发出警告,指出您正在使用没有原型的函数,这意味着您应该将原型放入头文件中,告诉编译器期望来自其他调用站点的调用,或者您应该将其设置为静态,告诉编译器它知道所有调用,并且可以使用静态分析进行优化)。
你认为是常数的东西,可能不会永远保持不变。原始代码将起作用。任何新代码很可能都不会。
没有 JIT 编译,你不能比你的 2^n 函数(以及由此产生的二进制大小)做得更好。 当然,您可以使用模板来避免将它们全部写出来。 为了防止源仅通过选择正确的实现而呈指数级扩展,您可以编写一个递归调度程序(演示):
template<bool... BB>
auto g() {return f<BB...>;}
template<bool... BB,class... TT>
auto g(bool b,TT... tt)
{return b ? g<BB...,true>(tt...) : g<BB...,false>(tt...);}
- 为什么此代码存在运行时错误?
- 编写了一个C++代码来检查表达式是否具有平衡括号并且我的代码未运行.我已经卡了一天了
- 如何减少代码的运行时间以对齐文本?
- 我不确定为什么代码没有运行,它在编译时抛出错误,说它无法找到 PrintArray()
- 当存在覆盖歧义函数时,代码如何运行?
- 为什么我的代码在运行时保持循环
- 如何使visual studio代码直接运行vcvarsx86_arm64.bat而不是vcvarsall.bat
- Vscode 自动格式化代码在运行几次后停止工作
- C++单例模式代码无法运行,不知道为什么?
- 为什么我的Arduino代码用于运行蓝牙控制的机器人,障碍物传感器无法正常工作
- 此代码中运行时间错误的原因是什么
- 代码正在运行,但不会执行 GPU 函数
- 致命错误:线程 1:EXC_BAD_ACCESS(代码 = EXC_I386_GPFLT)。我的代码无法运行
- C - 将代码注入运行过程(使用DLL)
- 为什么这个 c++ 代码在运行时给我分段错误?
- 在Visual Studio代码中运行代码的麻烦
- C 代码的运行时错误(基于指针)
- 其中一些代码无法运行
- C++代码永远运行并消耗内存
- "Segmentation fault"使用 Cmake 在C++代码中运行 python 函数时