C++ 当通过指针反序列化时,boost::serialization如何分配内存?

C++ 当通过指针反序列化时,boost::serialization如何分配内存?,c++,boost,boost-serialization,placement-new,C++,Boost,Boost Serialization,Placement New,简而言之,我想知道boost::serialization在通过指针进行反序列化时如何为对象分配内存。下面,您将找到我的问题的一个示例,与配套代码一起清楚地说明。这段代码应该功能齐全,编译良好,本质上没有错误,只是关于代码实际如何工作的问题 #include <cstddef> // NULL #include <iomanip> #include <iostream> #include <fstream> #include <string&

简而言之,我想知道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序列化命名空间的转发声明
//为了“教”boost如何使用非默认构造函数保存和加载类,必须重写这些函数
//在boost::serialization命名空间中。在这里制作原型。
命名空间boost{命名空间序列化{
模板
内联无效保存构造数据(存档和ar,常量非默认构造*ndc,常量无符号整数版本);
模板
内联无效加载构造数据(存档和ar,非默认构造*ndc,常量无符号整数版本);
}}
//下面是没有默认构造函数的实际类定义
类非默认构造函数
{
公众:
显式非默认构造函数(std::string initial)
:某些初始值{initial},状态{0}
{
}
std::string get_initial_value()const{return some_initial_value;}//For save_construct_data
私人:
std::字符串一些初始值;
int状态;
//请注意,我们在这里只序列化状态,而不是
//一些初始值传递到了ctor
好友类boost::serialization::access;
模板
无效序列化(存档和ar,常量未签名整数版本)
{

std::cout这是一个很好的示例程序,带有非常贴切的注释。让我们深入研究

// 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访问的任何重载(而不是重写)都足够了

跳转到它的核心:

// 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.
是的。文档说明了这一点。但这有点棘手,因为它是有条件的。原因是对象跟踪。比如说,我们序列化指向同一对象的多个指针,它们将被序列化一次

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


下面是一个简化的反例:

  • 演示日常生活能力
  • 演示目标跟踪
  • 删除所有转发声明(由于以下原因,它们是不必要的)
它用指针的10个副本序列化一个向量。我使用unique_ptr来避免泄漏实例(在main中手动创建的实例和通过反序列化创建的实例)

#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";
}
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++。 该库处理重载了
运算符new
的类型。在这种情况下,它调用
T::operator new
而不是globale
运算符new
。它总是按照您正确的推测传递
sizeof(T)

代码位于异常安全包装中:

是的,使用C++11或更高版本可以大大简化此代码。此外,析构函数中的空保护对于符合
运算符delete
要求的实现是冗余的

当然,
invoke\u new
invoke\u 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); }
    };
static T*invoke_new(){
typedef typename mpl::eval_if::type typex;
返回typex::invoke_new();
}
静态void invoke_delete(T*T){
typedef typename mpl::eval_if::type typex;
typex::invoke_delete(t);
}
结构有新的运算符{
static T*invoke_new(){返回static_cast((T::operator new)(sizeof(T));}
静态void调用_delete(T*T){(运算符delete)(T);}
};
结构没有新的运算符{
static T*invoke_new(){返回static_cast(运算符new(sizeof(T));}
静态void调用_delete(T*T){(运算符delete)(T);}
};

这里有一些条件编译和详细的注释,所以如果您想了解完整情况,请使用源代码。

这是一个很好的示例程序,注释非常贴切。让我们深入了解

// 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访问的任何重载(而不是重写)都足够了

跳转到它的核心:

// 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.
是的。文档说明了这一点。但这有点棘手,因为它是有条件的。原因是对象跟踪。比如说,我们序列化指向同一对象的多个指针,它们将被序列化一次

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


下面是一个简化的反例:

  • 演示日常生活能力
  • 演示目标跟踪
  • 删除所有转发声明(由于以下原因,它们是不必要的)
它用指针的10个副本序列化一个向量。我使用unique_ptr来避免泄漏实例(在main中手动创建的实例和通过反序列化创建的实例)

#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";
}
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++。 该库处理重载了
运算符new
的类型。在这种情况下,它调用
T::operator new
而不是globale
运算符new
。它总是按照您正确的推测传递
sizeof(T)

代码位于异常安全包装中:

是的,这个密码是sim卡