【问题标题】:Evaluate expressions until one returns true评估表达式,直到返回 true
【发布时间】:2019-01-30 13:11:56
【问题描述】:

我有一个savePotentiometerState(...) - 函数,如果有更改要保存,它返回true,如果什么都不做,则返回false。此外,我知道在通过我的主循环的任何一次通过时,最多有一个电位器可能已经改变(由于它们的读出方式)。

这是在一个非常受时间限制的嵌入式平台上,所以重要(或至少很重要)我不会比我必须更频繁地致电savePotentiometerState。但是,我想出的代码自然看起来很傻,很可能最终会出现在 thedailywtf:

if (!savePotentiometerState(pot1))
  if (!savePotentiometerState(pot2))
    ...
      if (!savePotentiometerState(potn));

另一种方法是使用短路评估:

const bool retval = savePotentiometerState(pot1) || savePotentiometerState(pot2) || ... || savePotentiometerState(potn);

我想我什至可以在这里放弃作业。但这也不是很好的风格,因为我滥用了 || 运算符的短路。

各种potn 对象是包含类的成员变量,因此没有明显的方法可以将其写成循环。

我觉得我在这里遗漏了一些明显的东西,所以我的问题是:是否有一种惯用/易于阅读的方式来做到这一点,并且不会牺牲效率?如果重要的话,我使用的是 C++17。

【问题讨论】:

  • 为什么你认为这会是短路算子的“荒谬”?这正是它的好处......
  • 为什么不拥有N 不同且不同的变量,而不是拥有N 元素的数组或向量?然后很容易从头到尾循环并在函数返回true时跳出循环。顺便说一句,它还会使添加或删除对象变得更加容易,因为不需要更改现有代码来反映新的或删除的变量。当然,还可以让你的班级不那么拥挤。
  • @user463035818 我猜是因为我以某种方式将|| 视为一个 logical 操作,我在这里直接使用它来控制程序流......但是你可能是对的,也许这是最好的方法。
  • 请提供minimal reproducible example。你的问题充满了奇怪的前提,这让你很难理解你真正的问题是什么
  • 每个人都在尝试矢量化,你们想回到循环吗?

标签: c++ idioms


【解决方案1】:

循环似乎是要走的路:

for (auto& pot : {std::ref(pot1), std::ref(pot2), /*..,*/ std::ref(potn)}) {
     if (savePotentiometerState(pot)) {
         break;
     }
}

【讨论】:

  • +1,这正是我正在寻找的替代品类型!不过,我需要检查这个循环是如何编译的:如果编译器实际上在堆栈上创建了引用数组(作为指针),这可能比每次只为所有罐子调用 savePotentiometerState 慢,它会还占用堆栈空间(系统总共有 96K RAM,因此这里的内存量相当小)。
【解决方案2】:

由于您可以使用 C++17,因此您可以利用 fold expressions 并编写一个辅助函数来为您进行评估。

template<typename... Args>
bool returtnPotentiometerState(Args&&... args)
{
    return (... || savePotentiometerState(args));
}

然后你会这样称呼它

if (returtnPotentiometerState(pot1, pot2, ..., potn))

这意味着你没有环路,你会短路。

【讨论】:

  • 我想就是这样:它应该不会比手动编写条件慢,我可以命名辅助函数来准确地表明代码的意图是什么.
  • @Timo 这很可能会更慢。由于它需要被引用,因此必须取消引用它才能获得它的价值,因为编译器无法知道是否有其他东西进行了修改。您的 const bool retval = 或 if 语句可能是最快的。 Jarods 也可能具有相同的速度,因为引用在范围内是本地的,因此编译器知道它们没有更改。您可以使用quick bench 进行测试。
  • 嗯,我想因为引用是本地的,并且假设帮助程序被内联,编译器应该很容易消除引用......但当然,假设是所有******的母亲,所以也许我明天会写快速基准测试(顺便说一句,谢谢你的链接!)。
  • @Timo 没问题。如果它是内联的,那么它应该不是问题。我只是想指出它可能会更慢。
  • @Hiroki 感谢您编写测试!事实上,循环似乎比天真的||ing 还要快,够有趣的!另一方面,将编译器更改为 GCC7.2 使天真的方式最快,折叠表达式紧随其后。就我而言,编译器实际上是 gcc-arm-none-eabi 7.2,所以我需要使用它来尝试基准测试,因为结果非常接近。
【解决方案3】:

就个人而言 - 我会避免使用您正在使用的算法。

我会一直保存每个底池的状态;并跟踪当前和以前的值;然后仅在值已更改时才调用给定的回调。

这样,savePotState 总是尽可能快地满足给定底池的需要;而且你永远不会进入 pot1 到 pot(n-1) 可以阻止 potn 被读取的状态。

【讨论】:

  • 优秀...可读,测试简单。饥饿(由硬件使用不当引起)可能很难诊断甚至识别。 (去过那里,做到了)
猜你喜欢
  • 2021-12-22
  • 1970-01-01
  • 1970-01-01
  • 2019-07-01
  • 2011-08-03
  • 2015-03-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多