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

#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 documentation when a class with a non-default constructor进行(反)序列化,将使用load / save_construct_data,但实际上我没有看到要为要加载的对象分配内存的地方,只是placement new在一个位置构造了一个对象。内存地址。但是,在那个地址分配了什么内存呢?

这条线的工作方式可能是一个误解:
::new(ndc)non_default_constructor(some_initial_value);
但我想知道我的误会在哪里。这是我的第一个问题,因此如果我对问题的提出方式有误,我深表歉意。谢谢你的时间。

最佳答案

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

// 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.

不用了通过ADL可以访问的任何重载(非覆盖)都足够,除了in-class选项。

跳到它的肉上:
// 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.

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

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

参见documentation

这是一个简化的反例:
  • 展示ADL
  • 展示对象跟踪
  • 删除所有前向声明(由于template POI,因此不必要)

  • 它使用10个指针副本序列化一个 vector 。我使用unique_ptr避免泄漏实例(既是在main中手动创建的实例,也是通过反序列化创建的实例)。

    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)

    该代码位于异常安全包装器中: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-guard对于operator delete的兼容实现是多余的。

    当然,现在现在是invoke_newinvoke_delete所在的位置。简述:
        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); }
        };
    

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

    关于c++ - 通过指针反序列化时,boost::serialization如何分配内存?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/62105624/

    10-11 22:59
    查看更多