当前位置: 首页 > 知识库问答 >
问题:

按正确顺序调用函数的零成本非宏解决方案

汤飞
2023-03-14

我有一个代码库,其中需要在一些唯一函数之前和之后按顺序调用一些常见函数

如:

common1();
common2();
unique();   // note: unique returns void but can have any number of arguments
common3();
common4();

问题是,任何时候创建一个新的独特的函数,或任何时候添加更多的commonX函数,那么代码中需要此模式的每个位置都必须匹配。

一种解决方案是使用宏

#define DO_UNIQUE_CORRECTLY(unique_code_block) \
  common1();           \
  common2();           \
  unique_code_block    \
  common3();           \
  commont();           \

...

void someFunc1(arg1) {
  DO_UNIQUE_CORRECTLY({
    unique1(arg1);
  });
}

void someFunc2(arg1, arg2) {
  DO_UNIQUE_CORRECTLY({
    unique2(arg1, arg2);
  });
}

这是可行的。问题是:有没有一种非宏C的方法可以在零开销的情况下做到这一点,并且阻止错误,并且不要过于冗长地使用。

注意:如果不明显,“不要过于冗长”的要求意味着我调用的函数可能有很多类型非常大的参数。必须复制和粘贴类型列表=“过于冗长”

我知道的一个解决方案是这样的

class Helper {
  Helper() {
    common1();
    common2();
  }
  ~Helper() {
    common3();
    common4();
  }
}

void someFunc1(arg1) {
  Helper helper;
  unique1(arg1);
}

void someFunc2(arg1, arg2) {
  Helper helper;
  unique2(arg1, arg2);
}

IMO这种解决方案的问题在于,似乎很容易在没有意识到自己做错了的情况下插入内容

void someFunc2(arg1, arg2) {
  Helper helper;
  doExtra1();     // bad
  unique2(arg1, arg2);
  doExtra2();     // bad
}

就像想象通用2执行记录开始时间通用3执行记录结束时间。在这种情况下,您不希望在unique2之前/之后有任何额外的东西。

你可以对宏提出同样的问题

void someFunc2(arg1, arg2) {
  DO_UNIQUE_CORRECTLY({
    doExtra1();     // bad
    unique2(arg1, arg2);
    doExtra2();     // bad
  });
});

但要正确重命名DO\u UNIQUE\u,请将其命名为TIME\u FUNCTION,并将Helper重命名为TimingHelper

void someFunc2(arg1, arg2) {
  TIME_FUNCTION({
    doExtra1();     // clearly bad
    unique2(arg1, arg2);
    doExtra2();     // clearly bad
  });
});

vs公司

void someFunc2(arg1, arg2) {
  TimingHelper helper;
  doExtra1();     // bad?
  unique2(arg1, arg2);
  doExtra2();     // bad?
}

计时助手(TimingHelper)在这里做一些神奇的事情。也许这只是一种观点,但这种辅助模式似乎比宏模式更容易出错。

它是否也可能是对辅助构造函数/析构函数的超小型但非零成本调用,而不是宏的内联代码?

还有一种就是用lambda

template<typename Func>
void DoUniqueCorrectly(Func fn) {
  common1();
  common2();
  fn();
  common3();
  common4();
}

void someFunc1(arg1) {
  DoUniqueCorrectly([&]() {
    unique1(arg1);
  });
}

void someFunc2(arg1, arg2) {
  DoUniqueCorrectly([&]() {
    unique2(arg1, arg2);
  });
}

据我所知,这一个的问题是,lambda实际上是在构建一个对象/元组来保存对闭包参数的引用。换言之,几乎是这样的

void someFunc2(type1 arg1, type2 arg2)
  std::tuple<const type1&, const type2&> c = {arg1, arg2};
  DoUniqueCorrectly([&]() {
    unique2(std::get<0>(c), std::get<1>(c));
  });
}

所以初始化这个元组会有开销。这些都会被优化吗?这样代码的开销就和宏一样了?

让我再加一条皱纹<代码>uniqueX可以是更复杂的函数调用。例如

void SomeClass::someFunc2(arg1, arg2) {
  DO_UNIQUE_CORRECTLY({
    getContext()->getThing()->unique2(arg1, arg2);
  });
}

还有别的解决办法吗?

PS:我知道如果存在任何开销,它是很小的。我仍然想知道是否有零开销的非宏C方法来做到这一点。

共有3个答案

岳毅
2023-03-14

这是一个基于模板的解决方案,其中不传递额外的参数(lambdas、std::function或其他),编译器可以将所有内容优化到“裸C”。如果需要,它还可以方便地扩展并专门化为具有(non-code>void)返回类型的唯一(),但为了简洁起见,将其省略。

#include <iostream>
#include <utility>

namespace {
using std::cout;
using std::forward;

void before() { cout << "before\n"; }
void after() { cout << "after\n"; }

void unique0() { cout << "unique0()\n"; }
void unique1(int a) { cout << "unique1(" << a << ")\n"; }
void unique2(int a, int b) { cout << "unique2(" << a << ", " << b << ")\n"; };

template <typename... A>
struct DoUniqueCorrectly {
  template <void (*U)(A...), typename... AA>
  static void unique(AA&&... args) {
    before();
    U(forward<AA>(args)...);
    after();
  }
};
}  // namespace

int main() {
  DoUniqueCorrectly<>::unique<&unique0>();
  DoUniqueCorrectly<int>::unique<&unique1>(3);
  DoUniqueCorrectly<int, int>::unique<&unique2>(1, 2);
}
唐兴发
2023-03-14

使用帮助器,您就在正确的轨道上了。您在示例中遗漏了一个作用域块,因此析构函数不会延迟:

{
    Helper helper;
    unique1(arg1);
}

正如您所说,在块中插入其他代码或完全忘记块很容易。一种解决方案是将唯一函数传递给助手执行。(或使其成为函数而不是对象):

Helper helper(&unique1, arg1);
helper_fn(&unique1, arg1);

现在,助手可以运行构造函数(或函数)中的所有内容,并且不能插入任何内容。但它不太可读。也许您没有可以按名称调用的函数,但只想运行一些代码。您可以使用lambda,如您所述:

Helper helper([&]() { some; code; });

这看起来几乎像其他类似的代码构造,while(cond){some;code;} 。我们能让它看起来更像那样吗?给Helper(助手)一个接受可调用的操作符(操作符)怎么样?然后你可以写:

Helper() + [&]() { some; code; }

最后一个改进是,恐怕我们又回到了使用宏:

#define UNIQUE Helper() + [&]()

UNIQUE { some; code; }

我认为这是你能得到的最简洁的语法。

至于lambda的开销:在简单的情况下,编译器应该完全优化它。在复杂情况下,与复杂情况所需的时间相比,开销很小。如果不花时间,编译器将对其进行优化。尝试一下,看看在它没有得到优化之前,你必须做一个多么复杂的案例。很难预测编译器会做什么,你必须测试它。

有关这种通过适当的异常处理隐藏lambda的方法的完整示例,请查看声明性控制流。这实际上会延迟在范围末尾运行代码,并选择始终运行它或仅在成功或错误时运行它。但是您可以轻松地将其调整为您的用例,并且能够在您的UniQUE助手中处理错误可能是无价的。

白宏义
2023-03-14

您提供的装饰器可以轻松扩展以将任意数量的参数传递给您的可调用对象:

template<typename Func, class... Args>
void DoUniqueCorrectly(Func&& fn, Args&&... args) 
{
  common1();
  common2();
  std::invoke(std::forward<Func>(fn), std::forward<Args>(args)...);
  common3();
  common4();
}

那么您的用例称为:

void someFunc1(arg1) { DoUniqueCorrectly(unique1, arg1); }
void someFunc2(arg1, arg2) { DoUniqueCorrectly(unique2, arg1, arg2); }

所有内容都转发到可插入的函数模板(DoUniqueCorrectly)。没有额外或中间对象。此外,invoke将为任何类型的可调用(函数指针、lambda、成员函数指针等)生成正确的调用语法,而这是宏无法现成完成的。

被放大的例子(褶皱)被称为

DoUniqueCorrectly(&Thing::unique2, o.getContext()->getThing(), 1, 2);

i、 e.指向从对象指针调用的成员函数的指针<代码>标准::调用没有问题。

演示

 类似资料:
  • 打扰一下。我还在学习令人惊叹的JQuery语言。我遇到了一个问题,读了很多书,但仍然一团糟。希望你能指导我解决问题。 我有三个函数,它们执行三个post调用。它们返回一个文本变量,我最近将其解析为JSON(如果可用)。 问题是所有这些功能都很有效。。。当然,它们是异步的。我需要: 按顺序执行,就像它们是同步的 除此之外,(这是我的挑战之一)函数a接收到一个param1,但在执行后,代码将param

  • 我正在使用react.js应用程序,我记得我能够用pops将回调函数从一个孩子传递给一个家长,但我无法再次实现这一点(我希望它保持简单,没有通量库): 所以我的父母: 和我的孩子: 无论我尝试什么,我都会得到: 无法在showCirculares读取未定义的属性“props”; 我知道这将通过一个简单的任务解决,这是基本的React.js内容,只是我找不到解决方案!!我做错了什么?

  • 我们从右到左都知道并爱/恨作文: 什么是自然/从左到右组合的“最标准”操作符(如在某种公共库中):

  • 本文向大家介绍Javascript异步执行不按顺序解决方案,包括了Javascript异步执行不按顺序解决方案的使用技巧和注意事项,需要的朋友参考一下 案例分析: 比如执行懒加载时候,onscroll 事件触发多次事件时候会调用多次 ajax 回调事件,由于每个事件返回先后次序并不能保证和触发前一致,所以在数据响应返回后所添加的数据顺序就很在 push 到数组上顺序不一致。 例子1: 这里的并发“

  • 我有一个object的数组,我希望循环访问该数组并为每个元素调用服务,但对于每个元素,我希望只有当当前调用成功时才调用下一个元素,除非阻塞其余的。 有没有在序列模式下调用可观察到的数据而不是并行的?

  • 我一直在搜索用于解决以下问题的正确消息传递模式: 我有一个队列,它包含域中每个userid的消息,每个消息都是一个userChanged事件。业务要求是必须按照FIFO顺序处理特定用户ID的所有消息,如果在处理特定用户ID的消息期间发生错误,则在将该错误标记为已成功处理之前,不应再进行处理。必须处理所有消息,并且需要将此解决方案部署在集群ESB环境中。 我想将这些事件解复用到每个用户ID的FIFO