这是我过去几天一直在研究的一个语义优化问题,但我陷入了困境。我的真实程序在 RTOS(特别是 FreeRTOS)上运行,我需要生成任务(它们是简化的、非终止版本的线程)。C API 采用一个void (*)(void*)
作为任务的入口点和一个void*
参数。相当标准的票价。
我已经为一个任务编写了一个包装类,而不是做一个老式的实现,比如有一个必须被最终任务类覆盖的虚拟方法,我宁愿让 C++ 生成必要的参数存储通过可变参数模板和函数实现对象和粘合函数。
我已经使用 lambdas 和已经完成了此操作std::function
,std::bind
但它们似乎实现了一些膨胀,即直到运行时才解析函数目标。基本上与虚拟方法使用的机制相同。如果可能的话,我正在努力减少所有开销。与硬编码实现相比,每个实例的膨胀已达到约 200 字节。(这是在具有 128K 总闪存的 ARM Cortex-M3 上,我们只剩下大约 500 个字节。)我在该主题上发现的所有 SO 问题同样将函数的解析推迟到运行时。
这个想法是让代码:
- 将可变参数的衰减版本存储在堆上分配的对象中(这是一种简化;可以使用分配器代替),并将其作为
void*
参数传递, - 传递一个生成的调用岛函数作为入口点,带有签名
void(void*)
,使用存储的参数调用目标函数,以及 - (这是我无法弄清楚的部分)让编译器从目标函数的签名中推断出参数列表的类型,以遵循Don't Repeat Yourself原则。
- 请注意,函数指针及其参数类型在编译时是已知的和解析的,传递给函数的实际参数值直到运行时才知道(因为它们包括对象指针和运行时配置选项等内容)。
在下面的示例中,我必须实例化其中一项任务,因为Task<void (*)(int), bar, int> task_bar(100);
我宁愿编写Task<bar> task_bar(100);
或Task task_bar<bar>(100);
让编译器弄清楚(或以某种方式在库中告诉它)可变参数必须匹配指定函数的参数列表。
“显而易见”的答案将是某种模板签名,template<typename... Args, void (*Function)(Args...)>
但不用说,它不会编译。Function
第一个论点where 的情况也不是。
我不确定这是否可能,所以我在这里问你们想出了什么。为了简化问题,我省略了针对对象方法而不是静态函数的变体代码。
下面是一个有代表性的测试用例。我正在用 gcc 4.7.3 和-std=gnu++11
标志构建它。
#include <utility>
#include <iostream>
using namespace std;
void foo() { cout << "foo()\n"; }
void bar(int val) { cout << "bar(" << val << ")\n"; }
template<typename Callable, Callable Target, typename... Args>
struct TaskArgs;
template<typename Callable, Callable Target>
struct TaskArgs<Callable, Target> {
constexpr TaskArgs() {}
template<typename... Args>
void CallFunction(Args&&... args) const
{ Target(std::forward<Args>(args)...); }
};
template<typename Callable, Callable Target, typename ThisArg,
typename... Args>
struct TaskArgs<Callable, Target, ThisArg, Args...> {
typename std::decay<ThisArg>::type arg;
TaskArgs<Callable, Target, Args...> sub;
constexpr TaskArgs(ThisArg&& arg_, Args&&... remain)
: arg(arg_), sub(std::forward<Args>(remain)...) {}
template<typename... CurrentArgs>
void CallFunction(CurrentArgs&&... args) const
{ sub.CallFunction(std::forward<CurrentArgs>(args)..., arg); }
};
template<typename Callable, Callable Target, typename... Args>
struct TaskFunction {
TaskArgs<Callable, Target, Args...> args;
constexpr TaskFunction(Args&&... args_)
: args(std::forward<Args>(args_)...) {}
void operator()() const { args.CallFunction(); }
};
// Would really rather template the constructor instead of the whole class.
// Nothing else in the class is virtual, either.
template<typename Callable, Callable Entry, typename... Args>
class Task {
public:
typedef TaskFunction<Callable, Entry, Args...> Function;
Task(Args&&... args): taskEntryPoint(&Exec<Function>),
taskParam(new Function(std::forward<Args>(args)...)) { Run(); }
template<typename Target>
static void Exec(void* param) { (*static_cast<Target*>(param))(); }
// RTOS actually calls something like Run() from within the new task.
void Run() { (*taskEntryPoint)(taskParam); }
private:
// RTOS actually stores these.
void (*taskEntryPoint)(void*);
void* taskParam;
};
int main()
{
Task<void (*)(), foo> task_foo;
Task<void (*)(int), bar, int> task_bar(100);
return 0;
}