Boost serialization of a std::vector of pointers. Error: "unregistered class - derived class not registered or exported"

Boost serialization of a std::vector of pointers. Error: "unregistered class - derived class not registered or exported"

我正在使用 boost 来(反)序列化一些 classes。一切都很好,除了 class 有一个 std::vector<Artefact*> 成员(见下面的定义)。我对指针向量有疑问。

我可以完美地(反)序列化 class Artefact 的单个实例。

我无法序列化一个 class,它有一个指向 class Artefact.

的指针向量

我尝试这样做时出现的提升错误是:

"未注册 class - 派生 class 未注册或导出"

我得到了一些输出。归档文件:

22 serialization::archive 19 1 0
0 1 0
1 1 0
2 0 0 66 0

Class 定义

#include <boost/serialization/vector.hpp>
#include <boost/serialization/map.hpp>

...

class Tag {
public:
    bool tag = false;
};

class Artefact : public Tag {
public:
    std::vector<enjyn_Vertex> vertices;
    std::vector<unsigned int> indices;
    std::vector<enjyn_Texture*> textures;

    glm::vec3 position{ 0.0f };
    glm::vec3 scale{ 1.0f };
    glm::vec3 rotation{ 0.0f, 1.0f, 0.0f };
    float rotation_degrees{ 0.0f };
    glm::vec4 colour{ 1.0f, 1.0f, 1.0f, 1.0f };

    ...

    std::vector<Artefact> artefacts; // note, no problem serializing std::vector<Artefact> here

    ...

};


//Mainly static data and function members 
class State {
public:
...
    static std::map<std::string, bool> state;
...
}

class Event {
public:
...
    virtual void OnMouseFocus(); //Lots of virtual data members like this one here
..
}

//Large class with lots of data and function members.
class Enjyn : public Event, public State {
public:
    ...

    std::vector<Artefact*> artefacts;  // I believe this is the problem member 

    ...
}

免费的非侵入式(反)序列化功能

我为所有 class 我想要归档的内容提供了非侵入式免费序列化功能,包括 序列化所有基本 classes 和非内置数据成员的函数。

下面的序列化函数(反)完美地序列化了 Artefact 的单个实例:


#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
...
void serialize(Archive& ar, Artefact& a, const unsigned int version) {
    //ar.template register_type<Artefact>(); ; commented as classes exported see below
    ar& boost::serialization::base_object<enjyn_Tag>(a);
    ar& a.vertices;
    ar& a.indices;
    ar& a.textures;

    ar& a.position;
    ar& a.scale;
    ar& a.rotation;
    ar& a.rotation_degrees;
    ar& a.colour;
    ar& a.aabb;
    ar& a.bsphere;
    ar& a.tex_offset_x;
    ar& a.tex_offset_y;
    ar& a.tex_scale_x;
    ar& a.tex_scale_y;
    ar& a.name;
    ar& a.artefacts; //works fine for vector of non-pointer vector of Artefact.
}

然而,当我尝试使用 std::vector<Artefact*> 归档对象时,我得到: "未注册 class - 派生 class 未注册或导出"

void serialize(Archive& ar, Enjyn& e, const unsigned int version)
{
    //ar.template register_type<enjyn>(); commented as classes exported see below:
    ar& boost::serialization::base_object<Event>(e); // not sure whether this is necessary, no tracking requried of base classes.
    ar& boost::serialization::base_object<State>(e);
    ar.template register_type<Artefact>();

    ...

    ar& e.artefacts; //This is a std::vector<Artefact*> which I cant archive
                        //if i remove this and add include any 'normal' non-pointer
                        //built in types the serialization appears to work perfectly.
    ...
}

序列化程序函数

我正在像这样完美地序列化 Artefact 对象的单个实例:


if (e.primary_artefact) { //primary_artefact is a pointer to an instance of Artefact
    try {

        std::string filepath = "test_filename";
        std::ofstream o(filepath);
        {
            boost::archive::text_oarchive oa(o);
            oa << *e.primary_artefact;
        }
    }
    catch (std::ifstream::failure e) {
        std::cerr << "ERROR:IFSTREAM: " << e.what() << std::endl;
    }
    catch (boost::archive::archive_exception e) {
        std::cerr << "ERROR:BOOST SERIALISATION: " << e.what() << std::endl;
    }
}

尝试序列化 class 如果我在序列化函数中包含 Enjyn.artefacts (std::vector),使用此函数的 Enjyn 会产生错误:


void serialize_enjyn(enjyn& e, std::string& name) 
    try {

        std::string filepath = name;
        std::ofstream o(filepath);
        {
            boost::archive::text_oarchive oa(o);
            oa << e;
        }
    }
    catch (std::ifstream::failure e) {
        std::cerr << "ERROR:IFSTREAM: " << e.what() << std::endl;
    }
    catch (boost::archive::archive_exception e) {
        std::cerr << "ERROR:BOOST SERIALISATION: " << e.what() << std::endl;
    }
}


我发疯了,在我的主程序文件中为所有类型导出了 GUID,因此可以在存档中跟踪所有相关类型。并不是说这是特别必要的。只需要跟踪“'class Artefact'”。


#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT_GUID(glm::vec2, "vec2")
BOOST_CLASS_EXPORT_GUID(glm::vec3, "vec3")
BOOST_CLASS_EXPORT_GUID(glm::vec4, "vec4")
BOOST_CLASS_EXPORT_GUID(Artefact, "enjyn_Artefact")
BOOST_CLASS_EXPORT_GUID(Vertex, "enjyn_Vertex")
BOOST_CLASS_EXPORT_GUID(Texture, "enjyn_Texture")
BOOST_CLASS_EXPORT_GUID(Tag, "enjyn_Tag")
BOOST_CLASS_EXPORT_GUID(State, "enjyn_State")
BOOST_CLASS_EXPORT_GUID(Event, "enjyn_event")
BOOST_CLASS_EXPORT_GUID(Enjyn, "enjyn")

int main(int argc, char* argv[]){

讨论

请注意,Artefact 对象用作其他 classes 的基础。我没有注册那些 classes 或为它们编写序列化函数,因为 Artefact 没有虚函数,我试图直接序列化 Artefact(尽管也通过指针间接地序列化),并且注册文档说明:

事实证明,序列化的对象类型取决于基础class(本例中的基础)是否是多态的。如果 base 不是多态的,即如果它没有虚函数,那么 base 类型的对象将被序列化。任何派生的 classes 中的信息都将丢失。如果这是所需要的(通常不是),则无需其他努力。

问题似乎与 Enjyn 中的成员 std::vector<Artefact*> artefact 有关。当我从 Enjyn 的序列化函数中删除它时,Enjyn 序列化完美。我显然遗漏了一些基本的东西:

指针、std::vector、派生类型和基础 class 来自 boost 文档。

请帮忙!

如果您真的花时间把它变成一个正确的、独立的示例,您可能会发现没有问题:

Live On Coliru

#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/array.hpp>
#include <iostream>

// mocking stuff
namespace glm {
    struct vec3 {
        std::array<float, 3> a = {0,0,0};
        bool operator==(vec3 const& rhs) const = default;
    };
    struct vec4 {
        std::array<float, 4> a = {0,0,0,0};
        bool operator==(vec4 const& rhs) const =default;
    };
} // namespace glm

struct enjyn_Vertex {
    bool operator==(enjyn_Vertex const&) const = default;
};
struct enjyn_Texture {
    bool operator==(enjyn_Texture const&) const = default;
};

struct enjyn_Tag {
    bool tag                                = false;
    bool operator==(enjyn_Tag const&) const = default;
};

class Artefact : public enjyn_Tag {
  public:
    std::vector<enjyn_Vertex>   vertices;
    std::vector<unsigned int>   indices;
    std::vector<enjyn_Texture*> textures; // not owned, so leaked?

    glm::vec3 position{0.0f};
    glm::vec3 scale{1.0f};
    glm::vec3 rotation{0.0f, 1.0f, 0.0f};
    float     rotation_degrees{0.0f};
    glm::vec4 colour{1.0f, 1.0f, 1.0f, 1.0f};

    std::vector<Artefact> artefacts;

    bool operator==(Artefact const& rhs) const {
        if (!enjyn_Tag::operator==(rhs))
            return false;

        auto tie = [](Artefact const& a) {
            return std::tie(a.vertices, a.indices, /*a.textures,*/ a.position,
                            a.scale, a.rotation, a.rotation_degrees, a.colour,
                            a.artefacts);
        };
        if (tie(*this) != tie(rhs))
            return false;
        // compare textures indirectly
        if (textures.size() != rhs.textures.size())
            return false;
        for (size_t i = 0; i<textures.size(); ++i)
            if (*textures[i] != *rhs.textures[i])
                return false;

        return true;
    }

  private:
};

//Mainly static data and function members 
class State {
  public:
    static std::map<std::string, bool> state;

    bool operator==(State const&) const = default;
};
/*static*/ std::map<std::string, bool> State::state;

class Event {
  public:
    virtual void OnMouseFocus() {
    } // Lots of virtual data members like this one here
    bool operator==(Event const&) const = default;
};

//Large class with lots of data and function members.
class Enjyn
    : public Event
    , public State {
  public:
    // Rule Of Three...
    ~Enjyn() { for (auto a : artefacts) delete a; }
    Enjyn()             = default;
    Enjyn(Enjyn const&) = delete;
    Enjyn(Enjyn&&)      = default;
    Enjyn& operator=(Enjyn const&) = delete;

    std::vector<Artefact*> artefacts;

    bool operator==(Enjyn const& rhs) const {
        if (! Event::operator==(rhs)) return false;
        if (! State::operator==(rhs)) return false;

        // compare artefacts indirectly
        if (artefacts.size() != rhs.artefacts.size())
            return false;
        for (size_t i = 0; i<artefacts.size(); ++i)
            if (*artefacts[i] != *rhs.artefacts[i])
                return false;

        return true;
    }
};

#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
namespace boost::serialization {
    template <typename Archive> void serialize(Archive& ar, enjyn_Vertex&, unsigned) { }
    template <typename Archive> void serialize(Archive& ar, enjyn_Texture&, unsigned) { }
    template <typename Archive> void serialize(Archive& ar, enjyn_Tag& t, unsigned) { ar& t.tag; }
    template <typename Archive> void serialize(Archive& ar, glm::vec3&v, unsigned) { ar& v.a; }
    template <typename Archive> void serialize(Archive& ar, glm::vec4&v, unsigned) { ar& v.a; }
    template <typename Archive> void serialize(Archive& ar, Artefact& a, unsigned)
    {
        //ar.template register_type<Artefact>(); ; commented as classes exported see below
        ar& boost::serialization::base_object<enjyn_Tag>(a);
        ar& a.vertices;
        ar& a.indices;
        ar& a.textures;

        ar& a.position;
        ar& a.scale;
        ar& a.rotation;
        ar& a.rotation_degrees;
        ar& a.colour;
        //ar& a.aabb;
        //ar& a.bsphere;
        //ar& a.tex_offset_x;
        //ar& a.tex_offset_y;
        //ar& a.tex_scale_x;
        //ar& a.tex_scale_y;
        //ar& a.name;
        ar& a.artefacts; // works fine for vector of non-pointer vector of Artefact.
    }
    template <typename Archive> void serialize(Archive& ar, Event& e, unsigned)
    {
    }
    template <typename Archive> void serialize(Archive& ar, Enjyn& e, unsigned)
    {
        ar& e.state; // warning: serializing statics may not do what you want
        ar& e.artefacts;
    }
} // namespace boost

Enjyn bake_me_one_with_everything();

int main() {
    Enjyn const original = bake_me_one_with_everything();

    std::stringstream ss;
    {
        boost::archive::text_oarchive oa(ss);

        oa << original;
    }

    std::cout << ss.str() << "\n";

    {
        boost::archive::text_iarchive ia(ss);

        Enjyn e;
        ia >> e;
        std::cout << "Roundtrip equal: " << std::boolalpha << (e == original)
                  << std::endl;
    }
}

#include <random>
Enjyn bake_me_one_with_everything()
{
    Enjyn e;

    std::mt19937 urbg{std::random_device{}()};
    auto         frand =
        std::bind(std::uniform_real_distribution<float>{}, std::ref(urbg));
    auto r10 =
        std::bind(std::uniform_int_distribution<>{1, 10}, std::ref(urbg));

    for (int i = 0; i < r10(); ++i) { // very sloppy loop
        auto& a = *e.artefacts.emplace_back(new Artefact());
        a.artefacts.resize(r10());

        a.colour           = {frand(), frand(), frand()};
        a.position         = {{frand(), frand(), frand()}};
        a.rotation         = {{frand(), frand(), frand()}};
        a.rotation_degrees = frand() * 360;
        a.scale            = {{frand(), frand(), frand()}};
        a.tag              = r10() % 2;

        std::generate_n(back_inserter(a.indices), r10(), r10);
        a.vertices.assign(r10(), enjyn_Vertex{});
        a.textures.assign(r10(), new enjyn_Texture{});
    }
    return e;
}

打印类似

的东西

你的公主在另一座城堡里。

想象缺陷

我注意到继承的很多“无偿”使用(例如继承静态数据成员),但具体来说,您从 Event 继承 Enjyn,这是一种多态类型。

所以我最好的猜测是这包含您的实际问题。

否则,您应该可以从这个实时示例中走出来。

注意事项

您的代码演示文稿显示了一些顺序,表明您可能正在跨源文件分离代码。在这种情况下,不要陷入在导出定义之前在不包含所有相关存档类型的文件中注册类型的陷阱:docs

BOOST_CLASS_EXPORT in the same source module that includes any of the archive class headers will instantiate code required to serialize polymorphic pointers of the indicated type to the all those archive classes. If no archive class headers are included, then no code will be instantiated.