通过指针反序列化时,boost :: serialization如何分配内存? [英] How does boost::serialization allocate memory when deserializing through a pointer?

查看:55
本文介绍了通过指针反序列化时,boost :: serialization如何分配内存?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简而言之,我想知道boost :: serialization在通过指针反序列化时如何为对象分配内存.在下面,您将找到我的问题的一个示例,在伴随的代码中清楚地说明了该示例.该代码应具有完整的功能并可以很好地进行编译,本质上没有错误,仅是有关代码实际工作方式的问题.

In short, I'd like to know how boost::serialization allocates memory for an object when deserializing through a pointer. Below, you'll find an example of my question, clearly illustrated alongside companion code. This code should be fully functional and compile fine, there are no errors, per se, just a question on how the code actually works.

#include <cstddef> // NULL
#include <iomanip>
#include <iostream>
#include <fstream>
#include <string>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

class non_default_constructor; // Forward declaration for boost serialization namespacing below


// In order to "teach" boost how to save and load your class with a non-default-constructor, you must override these functions
// in the boost::serialization namespace. Prototype them here.
namespace boost { namespace serialization {
    template<class Archive>
    inline void save_construct_data(Archive& ar, const non_default_constructor* ndc, const unsigned int version);
    template<class Archive>
    inline void load_construct_data(Archive& ar, non_default_constructor* ndc, const unsigned int version);
}}

// Here is the actual class definition with no default constructor
class non_default_constructor
{
public:
    explicit non_default_constructor(std::string initial)
    : some_initial_value{initial}, state{0}
    {

    }

    std::string get_initial_value() const { return some_initial_value; } // For save_construct_data

private:
    std::string some_initial_value;
    int state;

    // Notice that we only serialize state here, not the
    // some_initial_value passed into the ctor
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive& ar, const unsigned int version)
    {
        std::cout << "serialize called" << std::endl;
        ar & state;
    }
};

// Define the save and load overides here.
namespace boost { namespace serialization {
    template<class Archive>
    inline void save_construct_data(Archive& ar, const non_default_constructor* ndc, const unsigned int version)
    {
        std::cout << "save_construct_data called." << std::endl;
        ar << ndc->get_initial_value();
    }
    template<class Archive>
    inline void load_construct_data(Archive& ar, non_default_constructor* ndc, const unsigned int version)
    {
        std::cout << "load_construct_data called." << std::endl;
        std::string some_initial_value;
        ar >> some_initial_value;

        // Use placement new to construct a non_default_constructor class at the address of ndc
        ::new(ndc)non_default_constructor(some_initial_value);
    }
}}


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

    // Now lets say that we want to save and load a non_default_constructor class through a pointer.

    non_default_constructor* my_non_default_constructor = new non_default_constructor{"initial value"};

    std::ofstream outputStream("non_default_constructor.dat");
    boost::archive::text_oarchive outputArchive(outputStream);
    outputArchive << my_non_default_constructor;

    outputStream.close();

    // The above is all fine and dandy. We've serialized an object through a pointer.
    // non_default_constructor will call save_construct_data then will call serialize()

    // The output archive file will look exactly like this:

    /*
        22 serialization::archive 17 0 1 0
        0 13 initial value 0
    */


    /*If I want to load that class back into an object at a later time
    I'd declare a pointer to a non_default_constructor */
    non_default_constructor* load_from_archive;

    // Notice load_from_archive was not initialized with any value. It doesn't make
    // sense to intialize it with a value, because we're trying to load from
    // a file, not create a whole new object with "new".

    std::ifstream inputStream("non_default_constructor.dat");
    boost::archive::text_iarchive inputArchive(inputStream);

    // <><><> HERE IS WHERE I'M CONFUSED <><><>
    inputArchive >> load_from_archive;

    // The above should call load_construct_data which will attempt to
    // construct a non_default_constructor object at the address of
    // load_from_archive, but HOW DOES IT KNOW HOW MUCH MEMORY A NON_DEFAULT_CONSTRUCTOR
    // class uses?? Placement new just constructs at the address, assuming
    // memory at the passed address has been allocated for construction.

    // So my question is this:
    // I want to verify that *something* is (or isn't) allocating memory for a non_default_constructor
    // class to be constructed at the address of load_from_archive.

    std::cout << load_from_archive->get_initial_value() << std::endl; // This works.

    return 0;

}

根据 boost :: serialization文档当要对具有非默认构造函数的类进行序列化时,将使用load/save_construct_data,但实际上并没有看到为要加载的对象分配内存的地方,恰好是placement new在内存地址处构造对象的地方.但是是什么分配了该地址的内存呢?

Per the boost::serialization documentation when a class with a non-default constructor is to be (de)serialized, the load/save_construct_data is used, but I'm not actually seeing a place where memory is being allocated for the object to be loaded into, just where placement new is constructing an object at a memory address. But what allocated the memory at that address?

这行的工作方式可能是一个误解:

It's probably a misunderstanding with how this line works:

::: new(ndc)non_default_constructor(some_initial_value);

但是我想知道我的误会在哪里.这是我的第一个问题,如果对我的提问方式有误,我深表歉意.谢谢您的宝贵时间.

but I'd like to know where my misunderstanding lies. This is my first question, so I apologize if I've made some sort of mistake on how I've asked my question. Thanks for your time.

推荐答案

这是一个出色的示例程序,带有非常恰当的注释.让我们深入.

That's one excellent example program, with very apt comments. Let's dig in.

// In order to "teach" boost how to save and load your class with a
// non-default-constructor, you must override these functions in the
// boost::serialization namespace. Prototype them here.

您不必这样做.除了in-class选项外,任何通过ADL访问的重载(非覆盖)都足够.

You don't have to. Any overload (not override) accessible via ADL suffices, apart from the in-class option.

跳到它的肉上

// So my question is this: I want to verify that *something* is (or isn't)
// allocating memory for a non_default_constructor
// class to be constructed at the address of load_from_archive.

是的.文档指出了这一点.但这有点棘手,因为它是有条件的.原因是对象跟踪.说,我们将多个指向同一对象的指针序列化,它们将被序列化一次.

Yes. The documentation states this. But it's a little bit trickier, because it's conditional. The reason is object tracking. Say, we serialize multiple pointers to the same object, they will get serialized once.

反序列化时,将在对象流中用对象tracking-id表示对象.只有第一个实例将导致分配.

On deserialization, the objects will be represented in the archive stream with the object tracking-id. Only the first instance will lead to allocation.

请参见文档.

这是一个简化的反例:

它将带有10个指针副本的向量序列化.我使用unique_ptr避免泄漏实例(既是在main中手动创建的实例,也是通过反序列化创建的实例).

It serializes a vector with 10 copies of the pointer. I used unique_ptr to avoid leaking the instances (both the one manually created in main, as well as the one created by the deserialization).

在Coliru上直播

Live On Coliru

#include <iomanip>
#include <iostream>
#include <fstream>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/vector.hpp>

namespace mylib {
    // Here is the actual class definition with no default constructor
    class non_default_constructor {
      public:
        explicit non_default_constructor(std::string initial)
                : some_initial_value{ initial }, state{ 0 } {}

        std::string get_initial_value() const {
            return some_initial_value;
        } // For save_construct_data

      private:
        std::string some_initial_value;
        int state;

        // Notice that we only serialize state here, not the some_initial_value
        // passed into the ctor
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive& ar, unsigned) {
            std::cout << "serialize called" << std::endl;
            ar& state;
        }
    };

    // Define the save and load overides here.
    template<class Archive>
    inline void save_construct_data(Archive& ar, const non_default_constructor* ndc, unsigned)
    {
        std::cout << "save_construct_data called." << std::endl;
        ar << ndc->get_initial_value();
    }
    template<class Archive>
    inline void load_construct_data(Archive& ar, non_default_constructor* ndc, unsigned)
    {
        std::cout << "load_construct_data called." << std::endl;
        std::string some_initial_value;
        ar >> some_initial_value;

        // Use placement new to construct a non_default_constructor class at the address of ndc
        ::new(ndc)non_default_constructor(some_initial_value);
    }
}

int main() {
    using NDC = mylib::non_default_constructor;
    auto owned = std::make_unique<NDC>("initial value");

    {
        std::ofstream outputStream("vector.dat");
        boost::archive::text_oarchive outputArchive(outputStream);

        // serialize 10 copues, for fun
        std::vector v(10, owned.get());
        outputArchive << v;
    }

    /*
        22 serialization::archive 17 0 0 10 0 1 1 0
        0 13 initial value 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
    */

    std::vector<NDC*> restore;

    {
        std::ifstream inputStream("vector.dat");
        boost::archive::text_iarchive inputArchive(inputStream);

        inputArchive >> restore;
    }

    std::unique_ptr<NDC> take_ownership(restore.front());
    for (auto& el : restore) {
        assert(el == take_ownership.get());
    }

    std::cout << "restored: " << restore.size() << " copies with " << 
        std::quoted(take_ownership->get_initial_value()) << "\n";
}

打印

save_construct_data called.
serialize called
load_construct_data called.
serialize called
restored: 10 copies with "initial value"

vector.dat 文件包含:

22 serialization::archive 17 0 0 10 0 1 1 0
0 13 initial value 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0

图书馆内部

您并不在乎,但是您当然可以阅读源代码.可以预见,它毕竟比您天真地期望的要复杂得多:这是C ++ .

该库处理的类型已经重载了 operator new .在这种情况下,它将调用 T :: operator new 而不是全局 operator new .在正确推测的情况下,它始终会通过 sizeof(T).

The library deals with types that have overloaded operator new. In that case it calls T::operator new instead of the globale operator new. It always passes sizeof(T) as you correctly surmised.

该代码位于异常安全包装中: detail/iserializer.hpp

The code lives in the exception-safe wrapper: detail/iserializer.hpp

struct heap_allocation {
    explicit heap_allocation() { m_p = invoke_new(); }
    ~heap_allocation() {
        if (0 != m_p)
            invoke_delete(m_p);
    }
    T* get() const { return m_p; }

    T* release() {
        T* p = m_p;
        m_p = 0;
        return p;
    }

  private:
    T* m_p;
};

是的,此代码在C ++ 11或更高版本中已大大简化.另外,析构函数中的NULL防护对于 operator delete 的兼容实现是多余的.

Yes, this code be simplified a lot with C++11 or later. Also, the NULL-guard in the destructor is redunant for compliant implementations of operator delete.

现在当然是 invoke_new invoke_delete 所在的位置.简明呈现:

Now of course, invoke_new and invoke_delete are where it's at. Presenting condensed:

    static T* invoke_new() {
        typedef typename mpl::eval_if<boost::has_new_operator<T>,
                mpl::identity<has_new_operator>,
                mpl::identity<doesnt_have_new_operator>>::type typex;
        return typex::invoke_new();
    }
    static void invoke_delete(T* t) {
        typedef typename mpl::eval_if<boost::has_new_operator<T>,
                mpl::identity<has_new_operator>,
                mpl::identity<doesnt_have_new_operator>>::type typex;
        typex::invoke_delete(t);
    }
    struct has_new_operator {
        static T* invoke_new() { return static_cast<T*>((T::operator new)(sizeof(T))); }
        static void invoke_delete(T* t) { (operator delete)(t); }
    };
    struct doesnt_have_new_operator {
        static T* invoke_new() { return static_cast<T*>(operator new(sizeof(T))); }
        static void invoke_delete(T* t) { (operator delete)(t); }
    };

有一些条件编译和冗长的注释,因此,如果您需要完整的图片,请使用源代码.

There's some conditional compilation and verbose comments, so per-use the source code if you want the full picture.

这篇关于通过指针反序列化时,boost :: serialization如何分配内存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆