Boost接口图序列化

Boost serialization of interface map

我创建了一个包含多种不同数据类型的地图。 s64、f64、数组、图像等 为此,我使用了 std::map<std::string, std::unique_ptr<MapEntryInterface>> database; 类型的地图。 我想存储它,并从文件系统重新加载它。但我听说地图不能用单行纸撕裂。所以我试着先存储一对的数据部分std::unique_ptr<MapEntryInterface> test;

    friend class boost::serialization::access;
    template<class Archive>
    void    serialize(Archive& ar, unsigned int version) {
        ar & test;
    }

程序在 ar & test 处崩溃并抛出异常:"unregistered class - derived class not registered or exported"。 什么问题?没看懂

这是最小代码:(已删除)

#include <boost/serialization/vector.hpp>
//....
};

正如 463035818_is_not_a_number 所指出的,我的狙击手没有用。 我重新创建了它,并且我认为还有很多。但是一旦我插入从文件加载函数,它就不再编译说: error C2280: "std::pair<const _Kty,_Ty>::pair(const std::pair<const _Kty,_Ty> &)" : Es wurde versucht, auf eine gelöschte Funktion zu verweisen

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

#include <fstream>
#include <iostream>

class MapEntryInterface {
public:
    MapEntryInterface(std::string type_str) : type(type_str) {}

    std::string type;

    friend class boost::serialization::access;
    template<class Archive>
    void    serialize(Archive& ar, unsigned int version) {
        if (type == "s64") {
            MapEntryS64* cast = (MapEntryS64*)this;
            cast->serialize(ar, version);
        }
        if (type == "f64") {
            MapEntryF64* cast = (MapEntryF64*)this;
            cast->serialize(ar, version);
        }
    }

};

class MapEntryS64 : public MapEntryInterface {
public:

    MapEntryS64(int init_val, const std::string& type_str)
        : data(init_val), MapEntryInterface(type_str)
    {}

    uint64_t data;
    friend class boost::serialization::access;
    template<class Archive>
    void    serialize(Archive& ar, unsigned int version) {
        ar & type;
        ar & data;
    }

};

class MapEntryF64 : public MapEntryInterface {
public:

    MapEntryF64(double init_val, const std::string& type_str)
        : data(init_val), MapEntryInterface(type_str)
    {}

    double data;
    friend class boost::serialization::access;
    template<class Archive>
    void    serialize(Archive& ar, unsigned int version) {
        ar & type;
        ar & data;
    }

};

class MapDataBase {
public:
    MapDataBase()
        //: test(std::unique_ptr<MapEntryInterface>(new MapEntryS64(381, "s64")))
    {
        database["key1"] = std::unique_ptr<MapEntryInterface>(new MapEntryS64(381, "s64"));
        database["key2"] = std::unique_ptr<MapEntryInterface>(new MapEntryF64(3.124512, "f64"));
    };

    bool SaveToFile() {
        std::ofstream ofs("boost_export.dat");
        if (ofs.is_open()) {
            boost::archive::text_oarchive oa(ofs);
            oa & *this;
            return true;
        }
        return false;
    }

    bool loadFromFile() {
        std::ifstream ifs("boost_export.dat");
        if (ifs.is_open())
        {
            try
            {
                boost::archive::text_iarchive ia(ifs);
                ia & *this;
                //std::string yolo;
                //ia >> yolo;
                //ia >> bam;
            }
            catch (std::exception& ex)
            {
                std::cout << ex.what() << std::endl;
                return false;
            }
        }

        return true;
    }

private:

    std::map<std::string, std::unique_ptr<MapEntryInterface>> database;
    //std::unique_ptr<MapEntryInterface> test;

    friend class boost::serialization::access;
    template<class Archive>
    void    serialize(Archive& ar, unsigned int version) {
        ar & database;
    }

};


void main() {

    MapDataBase tmp;
    tmp.SaveToFile();

    MapDataBase tmp2;
    //tmp2.loadFromFile();

}

我花了很多时间制作东西self-contained。众多变化中:

  1. 你不应该让基础 class 序列化派生(即 classic OOP),相反,Boost 期望派生 classes 序列化他们的 base_object<>(允许静态多态性,顺便说一句,类型注册)

  2. 当然,基class应该序列化它的数据成员(type)

  3. 基础 class 应该有一个虚拟析构函数(否则通过 unique_ptr 的析构函数删除将是未指定的

    Up till here:

    class MapEntryInterface {
      public:
        virtual ~MapEntryInterface() = default;
    
      protected:
        MapEntryInterface(std::string type_str) : type_(type_str) {}
        std::string type_;
    
        friend class boost::serialization::access;
        template <class Ar> void serialize(Ar& ar, unsigned) { ar& type_; }
    };
    using EntryPtr = std::unique_ptr<MapEntryInterface>;
    
  4. base/member 初始化列表的顺序具有误导性;无论如何初始化都是按声明顺序进行的

  5. type_str 是一种代码味道:OOP 虚拟化的整个思想是而不是 在任何地方切换类型。我通过至少默认值来为您 half-way ,但您可能完全没有它。毕竟类型 类型。

  6. 现在添加 base_object 序列化:

    template <class Ar> void serialize(Ar& ar, unsigned)
    {
        ar& boost::serialization::base_object<MapEntryInterface>(*this);
        ar& data_;
    }
    
  7. MapDatabase 得益于多项简化

    • 永远不要使用 newdelete
    • 之前检查流是多余的,因为您已经处理了异常
    • 因为loadFromFile没有处理异常的有用方法,re-throw,或者只是逃逸,
    • 还允许您将 MapDatabase 设为 return 类型而不是 bool
  8. saveToFileloadFromFile 可能需要一个文件名参数

  9. 未显示:可以说 saveToFileloadFromFile 不必是 MapDatabase

    的一部分

此时,添加一点代码来打印数据库内容:

Live On Wandbox

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

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

#include <fstream>
#include <iostream>

class MapEntryInterface {
  public:
    virtual ~MapEntryInterface() = default;
    virtual void print(std::ostream&) const = 0;

  protected:
    MapEntryInterface(std::string type_str) : type_(type_str) {}
    std::string type_;

    friend class boost::serialization::access;
    template <class Ar> void serialize(Ar& ar, unsigned) { ar& type_; }

    friend std::ostream& operator<<(std::ostream& os, MapEntryInterface const& e) {
        e.print(os);
        return os;
    }
};

using EntryPtr = std::unique_ptr<MapEntryInterface>;

class MapEntryS64 : public MapEntryInterface {
  public:
    MapEntryS64(int init_val = 0, const std::string& type_str = "s64")
        : MapEntryInterface(type_str)
        , data_(init_val)
    {
    }

  private:
    uint64_t data_;

    friend class boost::serialization::access;
    template <class Ar> void serialize(Ar& ar, unsigned) {
        ar& boost::serialization::base_object<MapEntryInterface>(*this);
        ar& data_;
    }

    virtual void print(std::ostream& os) const override {
        os << "S64(" << data_ << ", " << std::quoted(type_) << ")";
    }
};

class MapEntryF64 : public MapEntryInterface {
  public:
    MapEntryF64(double init_val = 0, const std::string& type_str = "f64")
        : MapEntryInterface(type_str)
        , data_(init_val)
    {
    }

  private:
    double data_;
    friend class boost::serialization::access;
    template <class Ar> void serialize(Ar& ar, unsigned)
    {
        ar& boost::serialization::base_object<MapEntryInterface>(*this);
        ar& data_;
    }

    virtual void print(std::ostream& os) const override {
        os << "F64(" << data_ << ", " << std::quoted(type_) << ")";
    }
};

class MapDatabase {
  public:
    using Map = std::map<std::string, EntryPtr>;

    MapDatabase() {
        database_.emplace("key1", std::make_unique<MapEntryS64>(381));
        database_.emplace("key2", std::make_unique<MapEntryF64>(3.124512));
    };

    bool SaveToFile(std::string const& filename) const
    {
        try {
            std::ofstream ofs(filename, std::ios::binary);
            boost::archive::text_oarchive oa(ofs);
            oa << *this;
            return true;
        } catch (std::exception& ex) {
            std::cout << ex.what() << std::endl;
            return false;
        }
    }

    static MapDatabase loadFromFile(std::string const& filename)
    {
        MapDatabase db;
        std::ifstream ifs(filename, std::ios::binary);
        boost::archive::text_iarchive ia(ifs);
        ia >> db;
        return db;
    }

    friend std::ostream& operator<<(std::ostream& os, MapDatabase const& mdb)
    {
        for (auto const& [k, b] : mdb.database_)
            if (b) os << std::quoted(k) << " -> " << *b << "\n";
            else   os << std::quoted(k) << " -> NULL\n";

        return os;
    }

  private:
    Map database_;

    friend class boost::serialization::access;
    template <class Ar> void serialize(Ar& ar, unsigned) { ar& database_; }
};

int main() {
    {
        MapDatabase tmp;
        std::cout << "------ tmp:\n" << tmp << "\n";
        if (not tmp.SaveToFile("boost_export.dat"))
            return 1;
    }

    MapDatabase roundtrip = MapDatabase::loadFromFile("boost_export.dat");
    std::cout << "------ roundtrip:\n" << roundtrip << "\n";
}

版画

------ tmp:
"key1" -> S64(381, "s64")
"key2" -> F64(3.12451, "f64")

unregistered class - derived class not registered or exported

unregistered class 运行时错误

消息说明了一切:在反序列化时,没有关于可以反序列化的类型的信息。添加:

#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT(MapEntryF64)
BOOST_CLASS_EXPORT(MapEntryS64)

现在打印

------ tmp:
"key1" -> S64(381, "s64")
"key2" -> F64(3.12451, "f64")

------ roundtrip:
"key1" -> S64(381, "s64")
"key2" -> F64(3.12451, "f64")

几个翻译单元

对于单独的翻译单元,根据文档拆分 class 导出宏。例如。 class 导出宏最终会做

#define BOOST_CLASS_EXPORT_GUID(T, K)                                  \
BOOST_CLASS_EXPORT_KEY2(T, K)                                          \
BOOST_CLASS_EXPORT_IMPLEMENT(T)                                        \
/**/

所以,天真地(用另一个 half-hour 明智地拆分它:)

  • 文件test.cpp

     #include "MapDatabase.h"
     #include <iostream>
    
     int main() {
         {
             MapDatabase tmp;
             std::cout << "------ tmp:\n" << tmp << "\n";
             if (not tmp.SaveToFile("boost_export.dat"))
                 return 1;
         }
    
         MapDatabase roundtrip = MapDatabase::loadFromFile("boost_export.dat");
         std::cout << "------ roundtrip:\n" << roundtrip << "\n";
     }
    
  • 文件MapDatabase.h

     #pragma once
     #include "MapEntryS64.h"
     #include "MapEntryF64.h"
     #include <boost/serialization/map.hpp>
     #include <boost/serialization/unique_ptr.hpp>
    
     class MapDatabase {
       public:
         using Map = std::map<std::string, EntryPtr>;
    
         MapDatabase();
    
         bool SaveToFile(std::string const& filename) const;
         static MapDatabase loadFromFile(std::string const& filename);
    
         friend std::ostream& operator<<(std::ostream&, MapDatabase const&);
    
       private:
         Map database_;
    
         friend class boost::serialization::access;
         template <class Ar> void serialize(Ar& ar, unsigned) { ar& database_; }
     };
    
  • 文件MapDatabase.cpp

     #include "MapDatabase.h"
     #include <boost/archive/text_iarchive.hpp>
     #include <boost/archive/text_oarchive.hpp>
     #include <fstream>
     #include <iostream>
    
     MapDatabase::MapDatabase() {
         database_.emplace("key1", std::make_unique<MapEntryS64>(381));
         database_.emplace("key2", std::make_unique<MapEntryF64>(3.124512));
     }
    
     bool MapDatabase::SaveToFile(std::string const& filename) const
     {
         try {
             std::ofstream ofs(filename, std::ios::binary);
             boost::archive::text_oarchive oa(ofs);
             oa << *this;
             return true;
         } catch (std::exception& ex) {
             std::cout << ex.what() << std::endl;
             return false;
         }
     }
    
     MapDatabase MapDatabase::loadFromFile(std::string const& filename)
     {
         MapDatabase db;
         std::ifstream ifs(filename, std::ios::binary);
         boost::archive::text_iarchive ia(ifs);
         ia >> db;
         return db;
     }
    
     std::ostream& operator<<(std::ostream& os, MapDatabase const& mdb)
     {
         for (auto const& [k, b] : mdb.database_)
             if (b) os << std::quoted(k) << " -> " << *b << "\n";
             else   os << std::quoted(k) << " -> NULL\n";
    
         return os;
     }
    
  • 文件MapEntryF64.h

     #pragma once
     #include "MapEntryInterface.h"
     #include <boost/serialization/base_object.hpp>
    
     class MapEntryF64 : public MapEntryInterface {
       public:
         MapEntryF64(int init_val = 0, const std::string& type_str = "f64");
    
       private:
         uint64_t data_;
    
         friend class boost::serialization::access;
         template <class Ar> void serialize(Ar& ar, unsigned) {
             ar& boost::serialization::base_object<MapEntryInterface>(*this);
             ar& data_;
         }
    
         virtual void print(std::ostream& os) const override;
     };
    
     #include <boost/serialization/export.hpp>
     BOOST_CLASS_EXPORT_KEY(MapEntryF64)
    
  • 文件MapEntryInterface.h

     #pragma once
     #include <iosfwd>
     #include <string>
     #include <memory>
     #include <boost/serialization/access.hpp>
    
     class MapEntryInterface {
       public:
         virtual ~MapEntryInterface() = default;
         virtual void print(std::ostream&) const = 0;
    
       protected:
         MapEntryInterface(std::string type_str);
         std::string type_;
    
         friend class boost::serialization::access;
         template <class Ar> void serialize(Ar& ar, unsigned) { ar& type_; }
    
         friend std::ostream& operator<<(std::ostream&, MapEntryInterface const&);
     };
    
     using EntryPtr = std::unique_ptr<MapEntryInterface>;
    
  • 文件MapEntryS64.h

     #pragma once
     #include "MapEntryInterface.h"
     #include <boost/serialization/base_object.hpp>
    
     class MapEntryS64 : public MapEntryInterface {
       public:
         MapEntryS64(int init_val = 0, const std::string& type_str = "s64");
    
       private:
         uint64_t data_;
    
         friend class boost::serialization::access;
         template <class Ar> void serialize(Ar& ar, unsigned) {
             ar& boost::serialization::base_object<MapEntryInterface>(*this);
             ar& data_;
         }
    
         virtual void print(std::ostream& os) const override;
     };
    
     #include <boost/serialization/export.hpp>
     BOOST_CLASS_EXPORT_KEY(MapEntryS64)
    
  • 文件MapEntryF64.cpp

     #include "MapEntryF64.h"
     #include <ostream>
     #include <iomanip>
    
     MapEntryF64::MapEntryF64(int init_val, const std::string& type_str)
         : MapEntryInterface(type_str)
         , data_(init_val)
     {
     }
    
     void MapEntryF64::print(std::ostream& os) const {
         os << "F64(" << data_ << ", " << std::quoted(type_) << ")";
     }
    
     BOOST_CLASS_EXPORT_IMPLEMENT(MapEntryF64)
    
  • 文件MapEntryInterface.cpp

     #include "MapEntryInterface.h"
    
     MapEntryInterface::MapEntryInterface(std::string type_str) : type_(type_str) {}
    
     std::ostream& operator<<(std::ostream& os, MapEntryInterface const& e)
     {
         e.print(os);
         return os;
     }
    
  • 文件MapEntryS64.cpp

     #include "MapEntryS64.h"
     #include <ostream>
     #include <iomanip>
    
     MapEntryS64::MapEntryS64(int init_val, const std::string& type_str)
         : MapEntryInterface(type_str)
         , data_(init_val)
     {
     }
    
     void MapEntryS64::print(std::ostream& os) const {
         os << "S64(" << data_ << ", " << std::quoted(type_) << ")";
     }
    
     BOOST_CLASS_EXPORT_IMPLEMENT(MapEntryS64)
    

打印:Live On Wandbox

------ tmp:
"key1" -> S64(381, "s64")
"key2" -> F64(3, "f64")

unregistered class - derived class not registered or exported

UHOH 我们注定要失败吗?

完全没有。只需要 read the documentation closely:

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.

因此,添加包含:

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
BOOST_CLASS_EXPORT_IMPLEMENT(MapEntryF64)

MapEntryS64 也一样):

Live On Wandbox

------ tmp:
"key1" -> S64(381, "s64")
"key2" -> F64(3, "f64")

------ roundtrip:
"key1" -> S64(381, "s64")
"key2" -> F64(3, "f64")

奖励:简单

我更喜欢简单。我可能会将以上所有内容替换为:

  • 文件MapDatabase.h

     #pragma once
     #include <map>
     #include <boost/variant.hpp>
    
     namespace Database {
         struct Nil { void serialize(auto&, unsigned) {} };
         using S64   = uint64_t;
         using F64   = double;
         using Entry = boost::variant<Nil, S64, F64>;
         using Map   = std::map<std::string, Entry>;
    
         std::string_view typeOf(Entry const&);
    
         void SaveToFile(std::string const& filename, Map const& m);
         [[nodiscard]] Map loadFromFile(std::string const& filename);
    
         std::ostream& operator<<(std::ostream&, Nil);
         std::ostream& operator<<(std::ostream&, Map const&);
     } // namespace Database
    
  • 文件MapDatabase.cpp

     #include "MapDatabase.h"
     #include <boost/archive/text_iarchive.hpp>
     #include <boost/archive/text_oarchive.hpp>
     #include <boost/serialization/map.hpp>
     #include <boost/serialization/variant.hpp>
     #include <fstream>
     #include <iomanip>
     #include <iostream>
    
     namespace Database {
         std::string_view typeOf(Entry const& e) {
             assert(e.which() < 3);
             return std::array{"Nil", "S64", "F64"}[e.which()];
         }
    
         void SaveToFile(std::string const& filename, Map const& m)
         {
             std::ofstream ofs(filename, std::ios::binary);
             boost::archive::text_oarchive oa(ofs);
             oa << m;
         }
    
         Map loadFromFile(std::string const& filename)
         {
             Map                           db;
             std::ifstream                 ifs(filename, std::ios::binary);
             boost::archive::text_iarchive ia(ifs);
             ia >> db;
             return db;
         }
    
         std::ostream& operator<<(std::ostream& os, Nil) { return os << "NULL"; }
         std::ostream& operator<<(std::ostream& os, Map const& m)
         {
             for (auto const& [k, v] : m)
                 os << typeOf(v) << "\t" << std::quoted(k) << " -> " << v << "\n";
             return os;
         }
     } // namespace Database
    
  • 文件test.cpp

     #include "MapDatabase.h"
     #include <iostream>
    
     int main() {
         SaveToFile("boost_export.dat",
                    Database::Map{
                        {"key1", 381ul},
                        {"key3", {}},
                        {"key2", 3.124512},
                    });
    
         std::cout << Database::loadFromFile("boost_export.dat");
     }
    

打印中Live On Wandbox

S64 "key1" -> 381
F64 "key2" -> 3.12451
Nil "key3" -> NULL