保存和加载函数指针到文件

Save and load function pointers to file

考虑以下代码:

typedef float (*MathsOperation)(float _1, float _2);
struct Data
{
    float x, y;
    MathsOperation op; 
};
Data data[100];

float Add(float _1, float _2){//add}
float Sub(float _1, float _2){//subtract}
float Mul(float _1, float _2){//multiply}
// other maths operations

for (int i = 0; i < 100; ++i)
{
    // assign one of the maths operators above to data struct member op
    // according to some condition (maybe some user input):
    if(condition1) data[i].op = &Add;
    if(condition2) data[i].op = &Sub;
    if(condition3) data[i].op = &Mul;
    // etc.
}

现在我想以某种方式将 data 数组保存到一个文件中并稍后加载它(也许在另一个不知道用于将运算符分配给每个数组的条件的程序中元素)。显然,每次我 运行 应用程序时,指针都会不同。所以,我的问题是最好的方法是什么?

您无论如何都不能将 "functions" 存储为数据,正如您所说,在外部媒体中存储指针是行不通的。所以,在这种情况下你必须做的是存储一个运算符值,例如

enum Operator
{ 
   Op_Add,
   Op_Sub,
   Op_Mul,
   Op_Largest    // For array size below.
};

而不是:

if(condition1) data[i].op = &Add;
if(condition2) data[i].op = &Sub;
if(condition3) data[i].op = &Mul;

有:

if(condition1) data[i].op = Op_Add;
if(condition2) data[i].op = Op_Sub;
if(condition3) data[i].op = Op_Mul;

因为它是一个整型值,所以可以存储在一个文件中,然后你可以这样做:

// Or `fin.read(reinterpret_cast<char*>(data), sizeof(data))
fin >> op >> x >> y; 

if (op == Op_Add) ... 
else if (op == Op_Sub) ... 

或者有一个用 op 索引的函数指针数组...换句话说:

typedef float (*MathsOperation)(float _1, float _2);
...

MathsOperation mathsOps[Op_Largest] = { &Add, &Sub, &Mul };

...
mathsOps[op](x, y);
...

如果我在你那里我会建立一个索引,你可以在那里注册你的运营商

static std::array<MathsOperation> MathsOperations;
MathsOperations.push_back(Add);
MathsOperations.push_back(Sub);
MathsOperations.push_back(Mul);

int getIdx(MathsOperation op) {
   return std::find(MathsOperations.begin(), MathsOperations.end(), op) - MathsOperations.begin();
}

并将其放入 .h 文件中,紧跟在 MathsOperation 定义之后

然后不保存函数指针,您可以只保存相关索引并在之后访问运算符

int           opidx = getIdx(Add);
MathsOperator op    = MathsOperator[idx];

不可移植,但如果您的所有功能都在同一个模块中,几乎肯定可以工作:

template<typename FuncT>
intptr_t FunctionPointerToId( FuncT* fptr )
{
    return reinterpret_cast<intptr_t>(fptr) - reinterpret_cast<intptr_t>(&Add);
}

template<typename FuncT>
FuncT* FunctionPointerFromId( intptr_t id )
{
    return reinterpret_cast<FuncT*>(i + reinterpret_cast<intptr_t>(&Add));
}

这假定您的实现保留同一模块中函数的相对地址(大多数平台确实保证这是特定于实现的行为,因为动态加载器依赖于此)。使用相对地址(aka "based pointers")允许它仍然工作,即使模块是一个共享库,每次加载在不同的基地址(例如 ASLR)。

不过,如果您的函数来自多个模块,请不要尝试此操作。

如果您有能力构建和维护函数列表,那么将索引存储到该列表中绝对是更好的方法(这些索引即使在重新链接后也可以保持良好状态,而相对代码地址会发生变化)。

每个函数都需要一些永久标识符。您保存此标识符而不是函数地址并在读取后恢复地址。

最简单的就是整数标识符,它是数组的索引

const MathsOperation Operations[] = { &Add, &Sub };

在这种情况下,您绝不能更改操作项的顺序。

如果不可能,使用字符串:

const std::map<std::string, MathsOperation> OpNames
{
  { "Add", &Add },
  { "Sub", &Sub },
};