这是我过去几天一直在研究的语义优化问题,我被困住了。我的真实程序运行在RTOS(特别是FreeRTOS)上,我需要生成任务(这是简单的,非终止版本的线程)。 C API为任务的入口点采用void (*)(void*)
,并为void*
参数。非常标准的票价。
我为一个任务编写了一个包装类,而不是做一个老式的实现,比如有一个必须被最终任务类覆盖的虚方法,我宁愿让C ++生成必要的参数存储对象和胶合函数通过可变参数模板和函数。
我已经使用lambdas和std::function
以及std::bind
完成了这项工作,但它们似乎实现了一些膨胀,即直到运行时才解析函数目标。基本上与虚拟方法相同的机制将使用。如果可能的话,我正试图减少所有开销。与硬编码实现相比,每个实例的膨胀大约为200字节。 (这是在ARM Cortex-M3上,总闪存为128K,我们只剩下大约500个字节。)我在主题上找到的所有SO问题同样推迟了函数的解析直到运行时。
我们的想法是代码:
void*
参数传递,void(void*)
,使用存储的参数调用目标函数,并在下面的示例中,当我更愿意写Task<void (*)(int), bar, int> task_bar(100);
或Task<bar> task_bar(100);
并且编译器数字时,我必须将其中一个任务实例化为Task task_bar<bar>(100);
out(或以某种方式在库中告诉它)可变参数必须与指定函数的参数列表匹配。
“显而易见”的答案是某种模板签名,如template<typename... Args, void (*Function)(Args...)>
,但不用说,这不会编译。 Function
是第一个参数的情况也不存在。
我不确定这是否可行,所以我在这里要求看看你们想出的是什么。为了简化问题,我省略了以对象方法而非静态函数为目标的变体代码。
以下是具有代表性的测试用例。我正在用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;
}
答案 0 :(得分:4)
开始使用一些元编程样板:
template<int...> struct seq {};
template<int Min, int Max, int... s> struct make_seq:make_seq<Min, Max-1, Max-1, s...> {};
template<int Min, int... s> struct make_seq<Min, Min, s...> {
typedef seq<s...> type;
};
template<int Max, int Min=0>
using MakeSeq = typename make_seq<Min, Max>::type;
帮助打开一个元组:
#include <tuple>
template<typename Func, Func f, typename Tuple, int... s>
void do_call( seq<s...>, Tuple&& tup ) {
f( std::get<s>(tup)... );
}
结果函数指针的类型:
typedef void(*pvoidary)(void*);
实际的主力。请注意,不会出现虚函数开销:
template<typename FuncType, FuncType Func, typename... Args>
std::tuple<pvoidary, std::tuple<Args...>*> make_task( Args&&... args ) {
typedef std::tuple<Args...> pack;
pack* pvoid = new pack( std::forward<Args>(args)... );
return std::make_tuple(
[](void* pdata)->void {
pack* ppack = reinterpret_cast<pack*>(pdata);
do_call<FuncType, Func>( MakeSeq<sizeof...(Args)>(), *ppack );
},
pvoid
);
}
这是一个删除一些decltype
样板的宏。在C ++ 17(可能是14)中,这不应该是必需的,我们可以从第二个推断第一个参数:
#define MAKE_TASK( FUNC ) make_task< typename std::decay<decltype(FUNC)>::type, FUNC >
测试工具:
#include <iostream>
void test( int x ) {
std::cout << "X:" << x << "\n";
}
void test2( std::string s ) {
std::cout << "S:" << s.c_str() << "\n";
}
int main() {
auto task = MAKE_TASK(test)( 7 );
pvoidary pFunc;
void* pVoid;
std::tie(pFunc, pVoid) = task;
pFunc(pVoid);
delete std::get<1>(task); // cleanup of the "void*"
auto task2 = MAKE_TASK(test2)("hello");
std::tie(pFunc, pVoid) = task2;
pFunc(pVoid);
delete std::get<1>(task2); // cleanup of the "void*"
}
而且,对于子孙后代,我的第一次刺,这很有趣,但错过了标记:
Old version(它执行函数的运行时绑定,导致调用voidary
函数不可避免地执行两次调用)
一个小问题 - 如果你没有std::move
任务中的参数(或以其他方式在该调用中引发move
,比如使用临时代码),你最终会得到引用它们而不是void*
中的副本。