有没有办法通过虚函数表重新构建一些保存的类?

4

我正在将一些对象复制到文件中,它们都来自同一个类。但是我想在加载它们后调用它们的函数以执行该类应该执行的操作,以下是我目前所做的:

#include <iostream>
#include <fstream>

using namespace std;

struct a
{
    virtual void print()
    {
        cout << "this is a.\n";
    };
};

struct b : public a
{
    virtual void print()
    {
        cout << "this is b.\n";
    }
};

int main()
{
    ofstream testofile("test.bin",ios::binary);
    a* tempa = new a;
    a* tempb = new b;
    testofile.write((char*)tempa,sizeof(a));
    testofile.write((char*)tempb,sizeof(b));
    testofile.flush();
    testofile.close();
    ifstream testifile("test.bin",ios::binary);
    a* x = (a*)new char[max(sizeof(a),sizeof(b))];
    testifile.read((char*)x,sizeof(a));
    x->print();
    testifile.read((char*)x,sizeof(b));
    x->print();
}

我的示例代码运行良好,但如果我注释掉保存部分并运行程序,则似乎新运行的应用程序的虚函数表无效(尽管我的代码没有改变)。问题在于我的文件管理器类不知道可能从我的基对象派生的所有可能对象,并且我想使用仅调用一次文件管理器来重建所有应用程序结构。当然,我的每个对象都有自己的保存/加载函数,但是文件管理器应该如何猜测适用于当前数据块的加载函数的位置?


这对我来说似乎是序列化。 - Baiyan Huang
3个回答

7
请不要那样做。从来没有。
基本上,您所做的是使用旧式强制转换将 a* 转换为 char*。这会在两个不相关的类型之间静默地执行 reinterpret_cast,并且高度依赖于实现。您无法依赖底层内存布局:它可能因为任何原因而更改(即使使用相同的编译器)。
如果您的类包含指针,则不能保证它们指向的数据在重新加载类时仍然存在(或者只是相同的)。
如果要提供序列化机制,请创建自己的 serialize()deserialize() 函数(它们甚至可以是您可以专门化的模板函数,或者只是常规成员函数,这真的无关紧要)。
当然,这需要更多工作,但出于可靠性的考虑。此外,这样做,您可以优化数据表示以适应任何存储类型(保存到磁盘,发送到网络等),甚至可以更改类界面并仍然保持与已序列化实例的兼容性。

更倾向于定义 operator<<operator>> 而不是 serialize/deserialize(这似乎非常 Java 中心化)。 - Martin York
@Martin:有时候你确实需要virtual分派……当然,你仍然可以实现<<>>来调用serialize/deserialize :) - Matthieu M.

1

你所编写的代码将不具备可移植性(字节序)。

例如,基类或结构体的虚函数表偏移可能会发生变化。Solaris和Aix编译器将vft放置在结构/类的末尾,而VC++则将其放置在单继承情况下的偏移量(0)处。我从未使用g++进行过检查,但有些有趣的事情是可能的。

除非您需要处理大量数据且不必长时间维护这些数据(即临时数据),否则我不建议直接编写对象。

vft的值取决于动态库(dll或其他)在虚拟内存中映射的位置。如果您编写自己的分配器并修补vft,则可能会起作用。但这相当危险。


0

您希望有一个序列化机制,使得派生类的serialize方法可以调用基类的serialize函数,然后首先序列化一些id来表示派生类型(typeid),然后再序列化自己的成员变量。
为了从序列化文件中重构对象,您需要一个工厂对象来读取文件并确定类型(从派生的serialize typeid中获取),创建该类型的对象,并调用对象的deserialize方法从文件中加载它并通过基类指针返回该对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接