为可变参数函数设计更好的API

时间:2013-07-27 13:20:32

标签: c++ api c++11 variadic-functions

我想用一个更现代的C ++ 11风格的API包装一个可变的C ++函数。 该函数来自this one

Pin instrumentation tramework
VOID LEVEL_PINCLIENT::INS_InsertCall(INS ins,
                                     IPOINT action,
                                     AFUNPTR funptr,
                                     ...)   

AFUNPTR声明为:

typedef VOID (*AFUNPTR)();

...是传递funptr的参数列表。该列表由参数描述符(IARG_TYPE枚举),可选参数值和终结符IARG_END构成,以表示列表的结尾。

这是一个用于在给定指令(ins)之前检测函数的用法示例,它将打印rAX寄存器的内容:

void print_rax_and_tid(long rax, THREADID tid) {
    cout << rax << endl << tid << endl;
}

...

INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)print_rax_and_tid,
               IARG_REG_VALUE, REG_RAX, // the value of rAX register
               IARG_THREAD_ID,          // the thread id
               IARG_END)

这里我们声明我们的函数将接受一个将保存寄存器值的参数。我们还要求该工具将rAX寄存器的值传递给函数。

请注意,每个函数参数由一个或两个描述符参数描述:

  • IARG_REG_VALUEREG_RAX)描述了(long rax
  • IARG_THREAD_ID)描述了(THREADID tid

Pin框架设置描述符以了解在运行时传递给用户函数的内容。

另请注意,函数参数的类型不能从参数描述符中自动推导出来。在我的例子中,所有描述符都是枚举,但它们描述了一个long和THREADID参数。

我想用C ++ 11提供的所有内容设计这个包装器API,可能能够传递lambda而不是函数指针,为参数列表添加一些类型安全性,使用可变参数模板等。

可能使用情况看起来像这样(但我愿意接受建议):

INS_InsertCall(ins, IPOINT_BEFORE, 
               [](long rax, THREADID tid) { cout << rax << endl << tid << endl; },
               IARG_REG_VALUE, REG_RAX,
               IARG_THREAD_ID)

2 个答案:

答案 0 :(得分:1)

我对这个API的评价并不是很多:http://coliru.stacked-crooked.com/view?id=045edb71ffca8062a9e016506e4b51f7-4f34a5fd633ef9f45cb08f8e23efae0a

struct REG_VALUE {
    IARG_TYPE arg = IARG_REG_VALUE;
    REG_TYPE reg;
    REG_VALUE(REG_TYPE r) :reg(r) {}
};
template<REG_TYPE reg_v>
struct REGISTER : REG_VALUE {
    REGISTER() : REG_VALUE(reg_v) {}
};

template<class func_type, class ...param_types>
VOID InsertCall(INS ins, IPOINT action, func_type funptr, 
    param_types... param_values) 
{ INS_InsertCall(ins, action, (AFUNPTR)funptr, param_values..., IARG_END); }

然后

InsertCall(ins, IPOINT_BEFORE, print_rax_and_tid, 
    REGISTER<REG_RAX>(), IARG_THREAD_ID);

我将寄存器设置为模板类型,因此您不必拥有类型/值对,然后自动生成IARG_END,但除此之外,我对API不够了解了解还有什么可以实现自动化。

答案 1 :(得分:0)

您可以使用模板方法

#include <iostream>

template <typename Runnable, typename... Types>
auto execute(Runnable f, Types... ts) -> decltype(f(ts...))
{
   return f(ts...);
}

// Some methods to test with:
void a() { std::cerr << __func__ << __LINE__ << "\n"; }
void b(int) { std::cerr << __func__ << __LINE__ << "\n"; }
void c(int, char) { std::cerr << __func__ << __LINE__ << "\n"; }
int d() { std::cerr << __func__ << __LINE__ << "\n"; return 0; }
int e(int) { std::cerr << __func__ << __LINE__ << "\n"; return 0; }
int f(int, char) { std::cerr << __func__ << __LINE__ << "\n"; return 0; }
int g() { std::cerr << __func__ << __LINE__ << "\n"; return 0; }
void g(int) { std::cerr << __func__ << __LINE__ << "\n"; }

int main()
{
   int tmp = 1;
   char tmp_2 = '0';
   execute(a);
   execute(b, tmp);
   execute(c, tmp, tmp_2);
   execute(d);
   execute(e, tmp);
   execute(f, tmp, tmp_2);
   execute([](int){ std::cerr << __func__ << __LINE__ << "\n"; }, 0);
   execute(b); // This won't compile, as too few arguments provided.
   execute<int()>(g); // Explicit template instantiation needed (typename Runnable only)
   execute<void(int)>(g, 0); // Explicit template instantiation needed (typename Runnable only)
}

如果要放弃函数的返回值,模板会变得更简单

template <typename Runnable, typename... Types>
void execute(Runnable f, Types... ts)
{
   f(ts...);
}

如你所见,这也适用于lambdas。如果函数名称不明确,则无法避免显式模板实例化。