简体   繁体   English

如何避免运行时检查运行编译后无法访问的代码部分?

[英]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? 有没有更好的方法来实现这一目标?

Ensuring that if statements are evaluated at compile time 确保在编译时评估if语句

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 
}

Automatically creating a lookup table of all possible callback functions 自动创建所有可能的回调函数的查找表

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 at runtime 在运行时使用查找表

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]; 
}

Example demonstrating how to read in tasks 演示如何读取任务的示例

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.

相关问题 特定于客户端的自定义的条件编译与运行时检查? - Conditional-compilation vs run-time checks for client-specific customization? 为什么下面的代码中没有编译或运行时错误? - Why there is no compilation or run-time error in the below code? 运行时错误检查/ RTC - Run-Time Error Checks /RTCs 在运行时选择模板参数时如何避免代码呈指数增长 - How to avoid exponential increase in code when choosing template arguments at run-time 该代码的运行时复杂度是多少? - What is the run-time complexity of this Code? 在ARM处理器上运行pcl :: MovingLeastSquares代码时出现双重释放或损坏运行时错误 - double free or corruption run-time error while running pcl::MovingLeastSquares code on ARM processor 如何在模板中定义浮点常量。 避免在运行时进行强制转换 - How to define floating point constants within template. Avoid casts at run-time 直接将树对象存储在源代码中,以避免在运行时增长 - Store Tree Object Directly in Source to Avoid Growing at Run-time 我的代码如何在编译时做一件事,而在运行时做另一件事? - How can my code do one thing at compile-time, but another thing at run-time? 如何使用运行时 dll 注入写入运行和临时记事本/txt 文件 - How to use run-time dll injecting to write into a Running and Temp notepad/txt file
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM