Boost::Serialization:如何避免双保存指针?(并得到一个free.c错误)



我目前有一个stl:list,其中包含一些基类和一些派生类。

我可以载入并保存这个列表,没有任何问题。使用BOOST_CLASS_EXPORT(…)宏,一切正常工作,直到我添加以下内容:


--------------------------------------------------------------------------------------

我需要一些对象,其中包含列表中其他对象的指针。

(不要这么抽象:这些是一些"游戏对象",它们有一些对象的引用称为"区域",两者都来自基本的Game-Class。)
--------------------------------------------------------------------------------------


现在我序列化了列表,每个对象都被单独序列化了。

ar  & characterList;

游戏对象包含以下代码:

template<class Archive>
void save(Archive & ar, const unsigned int version) const {
    ar & boost::serialization::base_object<M_Character>(*this);
    ar & area; // If I add this line, it will crash
}
template<class Archive>
void load(Archive & ar, const unsigned int version) const {
    ar & boost::serialization::base_object<M_Character>(*this);
    ar & area; // This one as well
}

所以我试图保存指向区域的指针,但是在我添加了这些行之后,程序会崩溃,我将得到free.c,它告诉我我释放了同一个指针两次。Boost还会给我一个未注册的类错误。(虽然我导出了区域类,它也可以在没有

的情况下工作)
ar & area;

)

区域将被序列化两次因为首先区域将从列表中保存然后从对象中保存。

我怎样才能避免这种情况?是否有可能保存整个对象的第一次和第二次只有指针?

或者我应该尝试一些完全不同的东西(从id列表中获取指针)

好的,我已经为你创建了一个演示:

struct Area
{
    Area(int i):id(i) {}
    int id;
};
struct List : boost::noncopyable
{
    std::vector<Area*> areas;
    ~List() {
        std::for_each(areas.begin(), areas.end(), std::default_delete<Area>());
    }
};
struct M_Character {
    virtual ~M_Character() {} 
};
struct GameObject : M_Character, boost::noncopyable
{
    Area* area;
    GameObject(Area* area = nullptr) : area(area) {}
};
BOOST_CLASS_EXPORT_GUID(GameObject, "GameObject")

注意

  • List对areas具有所有权。(因此,使List不可复制;否则复制List将导致双删除)
  • GameObject 是指指向List中的Area对象之一。因此,不应该删除销毁区域(列表拥有它们!)
示例程序:

int main()
{
    List l;
    for (int i = 0; i < 10; ++i)
        l.areas.push_back(new Area(i));
    std::unique_ptr<M_Character> obj, roundtrip;
    // build original obj
    obj.reset(new GameObject(l.areas[3])); // sharing the area pointer from the list
    std::string const serialized = serialize(obj.get());
    std::cout << serialized << 'n';
    std::cout << "-------------------------------------------------n";
    // create roundtrip
    roundtrip.reset(deserialize(serialized));
    std::cout << "EQUAL? " << std::boolalpha << (serialized == serialize(roundtrip.get())) << "n";
}

你会注意到它是如何运行的(Live On Coliru):

clang++ -std=c++11 -Os -Wall -pedantic main.cpp -lboost_system -lboost_serialization && ./a.out
22 serialization::archive 10 1 10 GameObject 1 0
0 1 0
1 2 1 0
2 3
-------------------------------------------------
EQUAL? true
但是,在Coliru上看不到的是,这会泄漏内存。Valgrind告诉你,在反序列化过程中分配的4个字节丢失了——很明显,这是GameObject中的Area

但嘿!是不是Boost序列化做对象跟踪序列化指针?

好问题。是的。但是(这很容易被忽略),它将只对同一对象图中的指针这样做。因此,您可以通过

来解决这个问题。
  1. 用游戏对象序列化列表(破坏封装,容易出错)
  2. 创建一个超级对象,作为"容器"(或者技术上:对象图的根)

    struct World : boost::noncopyable
    {
        List list;
        GameObject* the_object;
        World() : the_object(nullptr) {}
        ~World() { delete the_object; }
    private:
        friend boost::serialization::access;
        template<class Archive>
            void serialize(Archive & ar, unsigned) {
                ar & list;
                ar & the_object;
            }
    };
    

    现在我们可以序列化/反序列化整个对象图("World"),对象跟踪将按照您的预期工作:Live On Coliru

    clang++ -std=c++11 -Os -Wall -pedantic main.cpp -lboost_system -lboost_serialization   && ./a.out
    22 serialization::archive 10 0 0 0 0 0 0 10 0 3 1 0
    0 0 3
    1 1 3
    2 2 3
    3 3 3
    4 4 3
    5 5 3
    6 6 3
    7 7 3
    8 8 3
    9 9 4 1 0
    10 0 0 3 3
    -------------------------------------------------
    EQUAL? true
    

和没有更多的内存泄漏!

完整代码清单

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/vector.hpp>
struct Area
{
    Area(int i):id(i) {}
    int id;
  private:
    Area() { } // used only in deserialization
    friend boost::serialization::access;
    template<class Archive>
        void serialize(Archive & ar, unsigned) { ar & id; }
};
struct List : boost::noncopyable
{
    std::vector<Area*> areas;
    ~List() {
        std::for_each(areas.begin(), areas.end(), std::default_delete<Area>());
    }
  private:
    friend boost::serialization::access;
    template<class Archive>
        void serialize(Archive & ar, unsigned) { ar & areas; }
};
struct M_Character {
    virtual ~M_Character() {} 
  private:
    friend boost::serialization::access;
    template<class Archive>
        void serialize(Archive & /*ar*/, unsigned) { }
};
struct GameObject : M_Character, boost::noncopyable
{
    Area* area;
    GameObject(Area* area = nullptr) : area(area) {}
  private:
    friend boost::serialization::access;
    template<class Archive>
        void serialize(Archive & ar, unsigned) {
            ar & boost::serialization::base_object<M_Character>(*this);
            ar & area;
        }
};
BOOST_CLASS_EXPORT_GUID(GameObject, "GameObject")
#include <sstream>
        struct World : boost::noncopyable
        {
            List list;
            GameObject* the_object;
            World() : the_object(nullptr) {}
            ~World() { delete the_object; }
        private:
            friend boost::serialization::access;
            template<class Archive>
                void serialize(Archive & ar, unsigned) {
                    ar & list;
                    ar & the_object;
                }
        };
std::string serialize(World const& w)
{
    std::stringstream ss;
    boost::archive::text_oarchive oa(ss);
    oa << w;
    return ss.str();
}
void deserialize(std::string const& input, World& w)
{
    std::stringstream ss(input);
    boost::archive::text_iarchive ia(ss);
    ia >> w;
}
int main()
{
    World world;
    for (int i = 0; i < 10; ++i)
        world.list.areas.push_back(new Area(i));
    // build original obj
    world.the_object = new GameObject(world.list.areas[3]); // sharing the area pointer from the list
    std::string const serialized = serialize(world);
    std::cout << serialized << 'n';
    std::cout << "-------------------------------------------------n";
    // create roundtrip
    World roundtrip;
    deserialize(serialized, roundtrip);
    std::cout << "EQUAL? " << std::boolalpha << (serialized == serialize(roundtrip)) << "n";
}

相关内容

最新更新