[英]How to avoid run-time checks for running parts of code that become unreachable after compilation?
My program gets a couple of Boolean variables from the user, and their values won't change afterwards. 我的程序从用户那里得到几个布尔变量,之后它们的值不会改变。 Each Boolean variable enables a part of code.
每个布尔变量都支持一部分代码。 Something like this:
像这样的东西:
#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;
}
Now my question is, since the Boolean variables are fixed every time the program calls callback_function()
, is there a way to avoid the if
statements inside the callback function? 现在我的问题是,由于每次程序调用
callback_function()
都会修复布尔变量,有没有办法避免回调函数中的if
语句?
This is one way to avoid the run-time checks (implement a callback function for all permutations of the Boolean variables --- only two cases are shown below): 这是避免运行时检查的一种方法(为布尔变量的所有排列实现回调函数---下面只显示了两种情况):
#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;
}
The problem is I have to implement 2^n
different callback functions, if there are n
Boolean variables. 问题是如果有
n
布尔变量,我必须实现2^n
不同的回调函数。 Is there a better way to accomplish this? 有没有更好的方法来实现这一目标?
C++17 introduces if constexpr
, which does exactly this: 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 you have optimizations enabled, if constexpr
isn't necessary. 如果启用了优化,
if constexpr
不需要if constexpr
。 Even if you use a regular if
instead of if constexpr
, because the bools are now templated, the compiler will be able to eliminate the if
statements entirely, and just run the tasks. 即使您使用常规
if
而不是if constexpr
,因为bool现在是模板化的,编译器将能够完全消除if
语句,并且只运行任务。 If you look at the assembly produced here , you'll see that even at -O1
, there are no if statements in any of the callback
functions. 如果你看看这里生成的程序集 ,你会看到即使在
-O1
,任何callback
函数都没有if语句。
We can now use callback_function
directly as a function pointer, avoiding function<void()>
: 我们现在可以直接使用
callback_function
作为函数指针,避免function<void()>
:
int main() {
using callback_t = void(*)();
callback_t func = callback_function<true, false, true>;
// Do stuff with func
}
We can also name the bool
s by assigning them to constexpr variables: 我们也可以通过将它们分配给constexpr变量来命名
bool
:
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
}
You mentioned choosing between different callback functions at runtime. 您提到在运行时选择不同的回调函数。 We can do this pretty easily with a lookup table, and we can use templates to automatically create a lookup table of all possible callback functions.
我们可以使用查找表轻松地完成此操作,并且我们可以使用模板自动创建所有可能的回调函数的查找表。
The first step is to get a callback function from a particular index: 第一步是从特定索引获取回调函数:
// 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>;
}
Once we can do that, we can write a function to create a lookup table from a bunch of indexes. 一旦我们能够做到这一点,我们就可以编写一个函数来从一堆索引中创建一个查找表。 Our lookup table will just be a
std::array
. 我们的查找表只是一个
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>());
Here, callbackLookupTable
contains all 8 possible callback functions, where callbackLookupTable[i]
expands the bits of i
to get the callback. 在这里,
callbackLookupTable
包含了所有8个可能的回调函数,其中callbackLookupTable[i]
扩展了位i
得到回调。 For example, if i == 6
, then i
's bits are 110
in binary, so 例如,如果
i == 6
,那么i
的位是二进制的110
,所以
callbackLookupTable[6]
is callback_function<true, true, false>
callbackLookupTable[6]
是callback_function<true, true, false>
Using the lookup table is really simple. 使用查找表非常简单。 We can get an index from a bunch of
bool
s by bitshifting: 我们可以通过bithifting从一堆
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];
}
We can get the bool
s at runtime now, and just call getCallbackBasedOnTasks
to get the correct callback 我们现在可以在运行时获取
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();
}
Leave the code as it is. 保持代码不变。
Execution time of an "if" compared to writing to std::out is practically zero, so you are arguing over nothing. 与写入std :: out相比,“if”的执行时间几乎为零,因此您无所谓。 Well, unless you spend some time measuring the execution time as it is, and with the if's removed according to the values of the three constants, and found that there is a real difference.
好吧,除非你花一些时间测量执行时间,并根据三个常量的值删除if,并发现存在真正的差异。
At most, you might make the function inline or static, and the compiler will probably realise the arguments are always the same when optimisation is turned on. 最多可以使函数内联或静态,并且编译器可能会意识到在打开优化时参数始终是相同的。 (My compiler would give a warning that you are using a function without a prototype, which means you should have either put a prototype into a header file, telling the compiler to expect calls from other call sites, or you should have made it static, telling the compiler that it knows all the calls and can use static analysis for optimisations).
(我的编译器会发出警告,说明你正在使用没有原型的函数,这意味着你应该将原型放入头文件,告诉编译器期望来自其他调用站点的调用,或者你应该将它设置为静态,告诉编译器它知道所有调用并且可以使用静态分析进行优化)。
And what you think is a constant, might not stay a constant forever. 你认为是一个常数,可能不会永远保持不变。 The original code will work.
原始代码将起作用。 Any new code most likely won't.
任何新代码都很可能不会。
Short of JIT compilation, you can't do better than your 2^n functions (and the resulting binary size). 缺少JIT编译,你不能比你的2 ^ n函数(以及生成的二进制大小)做得更好。 You can of course use a template to avoid writing them all out.
您当然可以使用模板来避免全部写出来。 To prevent the source from scaling exponentially just from selecting the correct implementation, you can write a recursive dispatcher ( demo ):
要防止源只是从选择正确的实现开始按指数扩展,您可以编写递归调度程序( demo ):
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...);}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.