C/ c++模式:在超时时退出for()循环

C/C++ pattern: exiting a for() loop on elapsed timeout

本文关键字:for 退出 循环 超时 c++ 模式      更新时间:2023-10-16

在嵌入式C/c++编程中,编写这种类型的for循环是很常见的:

...
 for(int16_t i=0; i<5; i++)
 {
    if(true == unlockSIM(simPinNumber))
       return true;
    wait_ms(1000);
 }
 return false;
 ...

或者像这样的while()循环:

bool CGps::waitGpsFix(int8_t fixVal)
{
   int16_t iRunStatus=0, iFixStatus=0;
   _timeWaitGpsFix = CSysTick::instance().set(TIME_TO_WAIT_GPS);
   while(1)
   {
      bool bRet = _pGsm->GPSstatus(&iRunStatus, &iFixStatus);
      if( (true == bRet) && (1 == iRunStatus) && (iFixStatus >= fixVal) )
         return true;
      if(CSysTick::instance().isElapsed(_timeWaitGpsFix))
         return false;
      wait_ms(500);
   }
   return false;
}
//---------------------------------------------------------------------------

有什么众所周知的好模式有用,不写每次这么多行,但只是一个函数或方法调用?

对于For循环,您可以使用接受函数(必须返回一个布尔值)并在成功时返回的函数模板。对于while循环,事情变得更加复杂,但我想lambdas可以用作true和false条件。

循环:

#include <iostream>
template<int retries, int wait_time, typename FUNC, typename ...Args>
bool retry(FUNC& f, Args &&... args)
{
    for (int i = 0; i < retries; ++i)
    {
        if (f(std::forward<Args>(args)...)) return true;
        if (i < retries - 1)
        {
            std::cout << "waiting " << wait_time << "n";
        }
    }
    return false;
}
bool func(int i)
{
    return (i > 0);
}
bool func2(int i, int j)
{
    return (i > j);
}
int main()
{
    bool result = retry<5, 500>(func, 0);
    std::cout << result << "n";
    result = retry<5, 500>(func, 1);
    std::cout << result << "n";
    result = retry<5, 500>(func2, 1, 2);
    std::cout << result << "n";
    result = retry<5, 500>(func2, 1, 0);
    std::cout << result << "n";
}

参见coliru

中的示例

使用围绕执行的习惯用法非常简单,它在由传递给该代码段的函数控制的环境/环境集中执行给定的代码段。在这里,我们只是在循环中每n毫秒调用一次这段代码,可以是一段固定的时间,也可以是一段固定的次数。


由于您在嵌入式环境中工作,并且似乎正在使用一组不同于<chrono><thread>提供的定时机制,我试图调整我的答案,以便您可以使用您似乎可以访问的方法来做同样的事情。这些是我在我的解决方案中使用的函数:

// similar functions to what you seem to have access to
namespace timing{
    // interval defined as some arbitrary type
    interval getInterval(int msCount);
    bool intervalElapsed(interval i);
    void await(interval i);
}

关于await函数的注意-它需要一个间隔,并暂停执行,直到间隔过去。似乎最接近这个的方法可能是等待总的毫秒数,尽管该策略会在计时中引入一定数量的漂移。如果你能忍受(鉴于你正在使用它,似乎你可以),那么很好。

在给定上述函数签名的情况下,重试-for变体看起来像这样:

template <typename Func>
bool pollRetries(
        int retryLimit, 
        int msInterval, 
        Func func){
    for(int i = 0; i < retryLimit; ++i){
        auto interval = timing::getInterval(msInterval);
        if (func()){return true;}
        timing::await(interval);
    }
    return false;
}

和重试时看起来像这样:

template <typename Func>
bool pollDuration(
        int msLimit, 
        int msInterval, 
        Func func){
    auto limit = timing::getInterval(msLimit);
    while(!timing::intervalElapsed(limit)){
        auto interval = timing::getInterval(msInterval);
        if (func()){return true;}
        timing::await(interval);
    }
    return false;
}

Coliru现场演示

两个函数都接受一个函数,该函数调用时不带参数,返回布尔值或可转换为布尔值的值。如果求值返回true,循环将退出,函数将返回true。否则,它将在重试计数或周期结束时返回false。

您的示例代码将转换为:

retry for loop:

return pollRetries(5,1000,[simPinNumber](){
    return unlockSIM(simPinNumber);
});

retry while loop:

return pollDuration(TIME_TO_WAIT_GPS, 500, [fixVal, this](){
    int16_t 
        iRunStatus = 0,
        iFixStatus = 0;
    bool bRet = this->_pGsm->GPSstatus(&iRunStatus, &iFixStatus);
    return bRet && (1 == iRunStatus) && (iFixStatus >= fixVal);
});

可以将函数函数或函数指针传递给这些方法并执行,因此也可以直接传递要执行的lambdas。关于这一点有几点注意事项:

  1. 没有捕获组的lambda可以用一元+操作符转换为函数指针,允许模板使用模板的函数指针实例化,而不是基于lambda的函数指针实例化。您可能需要这样做,因为:
  2. 函数中的每个lambda都有一个匿名类型。将lambda传入模板函数将导致额外的模板实例化,这可能会增加二进制大小。
  3. 你也可以通过为共享一组持久状态的使用定义自己的函数类来缓解上述问题。
  4. 你可以尝试把计时函数做成可变的模板,每个@stefaanv的解决方案。如果你走这条路,你可以从lambdas中删除捕获组,并手动传递该信息,这将允许你将lambdas转换为函数指针,就像它们是无状态的一样。
  5. 对于单个类的大多数重试循环,您可以简单地将重试机制定义为成员函数模板,然后使自己服从成员函数指针,从而使用被调用对象传递状态。但我不建议这样做,因为成员函数指针处理起来有点麻烦,您可以通过使无状态lambda接受对对象的引用并将*this传递给调用来获得相同的结果。您还必须将所有代码位定义为它们自己的函数,而不是简单地在使用它们的函数中定义它们。