为可变函数设计一个更好的API

Designing a better API for a variadic function

本文关键字:一个 更好 API 函数      更新时间:2023-10-16

我想用更现代的C++11风格的API包装一个可变的C++函数。该功能来自Pin仪器Trameworks:

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

其中AFUNPTR声明为:

typedef VOID (*AFUNPTR)();

并且CCD_ 2是要传递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框架将描述符设置为知道在运行时传递给用户函数的内容。

还要注意,函数参数的类型不能从参数描述符中自动推导出来。在我的示例中,所有描述符都是枚举,但它们描述了一个长的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)

我真的没有太多能想到与API有关的事情:http://coliru.stacked-crooked.com/view?id=045edb71ffca8062a9e016506e4b51f7-4f3a5fd633ef9f45cb08e23efe0a

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的了解还不够,无法理解还有什么可以自动化。

您可以使用模板方法

#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。如果函数名称不明确,则无法避免显式模板实例化。