簡體   English   中英

Boost 接口序列化 map

[英]Boost serialization of interface map

我創建了多種不同數據類型的 map。 s64、f64、Arrays、圖像等。為此,我使用了 map 類型的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 指出的那樣,我的狙擊手沒有用。 我重新創建了它,並且我認為還有很多。 但是一旦我從文件 function 插入負載,它就不再編譯說: 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();

}

我花了很多時間讓事情變得獨立。 在眾多變化中:

  1. 你不應該讓基類 class 序列化派生類(這是經典的 OOP),相反 Boost 期望派生類序列化他們的base_object<> (允許 static 多態性,順便說一句,類型注冊)

  2. 當然,base class應該序列化它的數據成員( type

  3. 基礎 class 應該有一個虛擬析構函數(否則通過unique_ptr的析構函數刪除將是未指定的

    到這里:

     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. 基本/成員初始化列表的順序具有誤導性; 無論如何初始化都是按聲明順序發生的

  5. type_str是一種代碼味道:OOP 虛擬化的整個想法不是在任何地方都打開類型。 我至少通過默認值為您做了一半,但您可能完全沒有它。 畢竟類型就是類型。

  6. 現在添加base_object序列化:

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

    • 永遠不要使用newdelete
    • 之前檢查流是多余的,因為您已經處理了異常
    • 由於loadFromFile沒有處理異常、重新拋出或只是讓其轉義的有用方法,
    • 還允許您將MapDatabase作為返回類型而不是bool
  8. saveToFileloadFromFile應該有一個文件名參數

  9. 未顯示:可以說saveToFileloadFromFile不需要是MapDatabase的一部分

此時,添加一點代碼來打印數據庫內容:

現場直播

#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)                                        \
/**/

所以,天真地(再花半個小時來明智地把它全部分開:)

  • 文件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 我們注定要失敗嗎?

一點也不。 只需要仔細閱讀文檔

包含任何存檔BOOST_CLASS_EXPORT標頭的同一源模塊中的 BOOST_CLASS_EXPORT 將實例化將指定類型的多態指針序列化到所有這些存檔類所需的代碼。 如果不包含存檔 class 標頭,則不會實例化任何代碼

因此,添加包括:

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

(對於MapEntryS64也是如此):

現場直播

------ 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"); }

在 Wandbox 上實時打印

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM