I'm getting bad weak pointers after serializing then deserializing objects that have-a shared pointer to virtual base class, using Boost.Serialization.
在使用Boost.Serialization序列化然后反序列化具有-指向虚拟基类的共享指针的对象之后,我得到了错误的弱指针。
My minimal example demonstrates:
我的最小示例演示了:
- Abstract base class
Base
, which has a shared pointer to Base
with default value nullptr.
- Concrete derived class
SharedPtrHolder
. This type overrides virtual method check_shared_from_this
which uses shared_from_this
. This call fails due to bad weak pointer after de-serialization of another type HasASharedPtrHolder
- Type
HasASharedPtrHolder
, owning a shared pointer to Base.
The problem: after de-serialization, calls to shared_from_this
fail due to bad weak pointers. I'm baffled -- after deserialization HasASharedPtrHolder
in fact has shared pointers, they don't fail to deserialize. But I can't call shared_from_this
on them!!!
问题:在反序列化之后,由于错误的弱指针,对Shared_From_This的调用失败。我很困惑--反序列化HasASharedPtrHolder实际上已经共享了指针,它们不会失败反序列化。但我不能对他们调用Shared_from_This!
My real use case uses visitor pattern, and the bad weak pointers occur in the Visit calls. Before serialization, fine. After, nope. I think this MWE demonstrates the problem.
我的实际用例使用访问者模式,错误的弱指针出现在访问调用中。在序列化之前,很好。之后,不会了。我认为MWE说明了这个问题。
I would love help with this. Thanks!
我很乐意帮忙。谢谢!
MWE:
MWe:
#include <memory>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/nvp.hpp>
//this #define MUST appear before #include <boost/test/unit_test.hpp>
#define BOOST_TEST_MODULE "MWE"
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#include <fstream>
// abstract, and owns a pointer to Base.
class Base{
public:
Base() = default;
virtual ~Base() = default;
void set_ptr(std::shared_ptr<Base> const& n){
this->_next = n;
}
const std::shared_ptr<Base> get_ptr() const{
return this->_next;
}
virtual void do_thing_using_shared_from_this(){
throw std::runtime_error("failed to override, this is the base");
}
virtual void print(){
std::cout << "this is base, this should have been overridden" << std::endl;
}
private:
std::shared_ptr<Base> _next = nullptr;
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned version) {
ar & _next;
}
};
// concrete, with a method using `shared_from_this`
// this is a proxy for a visitable type.
class SharedPtrHolder: public virtual Base, public std::enable_shared_from_this<SharedPtrHolder>
{
public:
SharedPtrHolder() = default;
virtual ~SharedPtrHolder() = default;
void check_shared_from_this() const{
std::shared_ptr<const SharedPtrHolder> another_shared_ptr = this->shared_from_this();
}
virtual void do_thing_using_shared_from_this() override
{
auto as_shared = this->shared_from_this();
}
virtual void print() override{
std::cout << "this is derived" << std::endl;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned version) {
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base);
}
};
// this is a proxy for a type that owns visitable pointers.
class HasASharedPtrHolder{
public:
HasASharedPtrHolder(std::shared_ptr<Base> const& p): _ptr(p)
{}
HasASharedPtrHolder() = default;
~HasASharedPtrHolder() = default;
void set_ptr(std::shared_ptr<Base> const& n){
this->_ptr = n;
}
void check_shared_from_this() const{
_ptr->do_thing_using_shared_from_this(); // this call fails after de-serialization, due to bad weak pointers
}
void call_a_virtual_function(){
_ptr->print();
}
private:
std::shared_ptr<Base> _ptr = nullptr;
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned version) {
ar.template register_type<SharedPtrHolder>(); // have to register, because have pointer to base
ar & _ptr; // serialize the data this type owns
}
};
BOOST_AUTO_TEST_SUITE(boost_serialization)
BOOST_AUTO_TEST_CASE(serialize_deserialize_has_a)
{
{ // to create a scope
auto a_ptr = std::make_shared<SharedPtrHolder>();
auto b_ptr = std::make_shared<SharedPtrHolder>();
b_ptr->set_ptr(a_ptr);
HasASharedPtrHolder sys(b_ptr);
std::ofstream fout("serialization_basic");
boost::archive::text_oarchive oa(fout);
sys.check_shared_from_this();
// write class instance to archive
oa << sys;
}
{
std::ifstream fin("serialization_basic");
boost::archive::text_iarchive ia(fin);
// read class state from archive
HasASharedPtrHolder sys;
ia >> sys;
sys.call_a_virtual_function(); // this call is fine
sys.check_shared_from_this(); // bad weak pointer, due to shared_from_this
}
}
BOOST_AUTO_TEST_SUITE_END()
更多回答
What is your MWE's compile line? Mine is almost working: /usr/bin/clang++ -std=c++17 -Weverything -Wno-c++98-compat -Wno-padded -Wno-poison-system-directories -Wno-unqualified-std-cast-call -DBOOST_TEST_MODULE=a_test -DBOOST_LOG_TRIVIAL -Wno-suggest-destructor-override -Wno-inconsistent-missing-destructor-override -Wno-used-but-marked-unused -Wno-c++98-compat-pedantic -Wno-disabled-macro-expansion -Wno-global-constructors -Wno-weak-vtables -Wno-unused-parameter -isystem /boost/include -L/boost/lib -lboost_serialization -lboost_unit_test_framework -lboost_test_exec_monitor -lboost_log x.cpp
您的MWE的编译行是什么?我的测试即将开始工作:/usr/bin/clang++-std=c++17-WEverything-wno-c++98-comat-wno填充-wno-毒药-系统-目录-wno-不合格-std-cast-call-DBOOST_TEST_MODULE=a_test-DBOOST_LOG_TRIVEAL-wno-suggest-destructor-over-wno-dissistent-destructor-over-wno-已使用,但标记为unusat-unused-unused-wno-c++98-comat-pedtic-wno-Disable-宏-Expansion-wno-global-structors-wno-unusted参数-issystem/Boost/Include-L/Boost/lib-lBoost_Serialization-lBoost单元测试框架-lBoost_test_exec_monitor-lBoost_log x.cpp
Here's my compile line: g++ -std=gnu++14 -I/opt/homebrew/include -g boost_serialization_shared_ptr_test.cpp -L/opt/homebrew/lib -lboost_unit_test_framework-mt -lboost_serialization-mt
(i'm on MacOS with homebrew-provided boost)
下面是我的编译行:g++-std=gnu++14-i/opt/home brew/Include-g Boost_Serialization_Shared_PTR_Test.cpp-L/opt/HOME BREW/lib-lBoost_unit_test_framework-mt-lBoost_Serialization-mt(我在MacOS上使用HOME BREW提供的Boost)
i edited my post to include a few lines to get it to compile standalone, prominently #define BOOST_TEST_DYN_LINK
to resolve a missing symbol for main
我编辑了我的帖子,使其包含几行代码以使其编译独立的,突出显示的是#定义BOOST_TEST_DYN_LINK以解决Main中缺少的符号
I suspect that the object was not constructed using std::make_shared<SharedPtrHolder>()
, so it would be undefined behavior to this->shared_from_this();
where the test is failing. I try to prevent that scenario in my own code by making all the constructors private (using the private key idiom), and using a static class factory function that ensures the objects are always created via std::make_shared<SharedPtrHolder>()
.
我怀疑该对象不是使用std::Make_Shared()构造的,因此在测试失败的情况下,对于this->Shared_from_This(),这将是未定义的行为。我试图在自己的代码中避免这种情况,方法是将所有构造函数设置为私有的(使用私钥习惯用法),并使用静态类工厂函数来确保对象始终通过std::Make_Shared()创建。
The problem was subtle. Excellent MWE in my opinion. +1
问题很微妙。在我看来,MWE很棒。+1
You're serializing through shared_ptr<Base>
. Base
does not publicly inheriting from enable_shared_from_this
. That's your problem.
您正在通过Shared_PTR进行序列化。Base不公开继承Enable_Shared_From_This。那是你的问题。
If you have nodes that require it, you need to serialize them as such.
如果您有需要它的节点,则需要将其序列化。
A slight reshuffle seems to make sense. Given the invariants a static_pointer_cast
should be enough to get shared_ptr<Node const>
from the shared_from_this()
pointer:
略微的改组似乎是有道理的。在给定不变量的情况下,STATIC_POINTER_CAST应该足以从Shared_from_This()指针获取Shared_PTR
:
Live On Coliru
科里鲁现场直播
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
// this #define MUST appear before #include <boost/test/unit_test.hpp>
#define BOOST_TEST_MODULE "MWE"
// #define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#include <fstream>
// abstract, and owns a pointer to Base.
using SBase = std::shared_ptr<class Base>;
class Base : public std::enable_shared_from_this<Base> {
public:
Base() = default;
virtual ~Base() = default;
void next(SBase const& n) { _next = n; }
const SBase next() const { return _next; }
virtual void check_shared() { throw std::runtime_error("not implemented"); }
virtual void print() { std::cout << "(abstract)" << std::endl; }
private:
SBase _next = nullptr;
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) { ar& _next; }
};
// concrete, with a method using `shared_from_this`
// this is a proxy for a visitable type.
class Node : public /*virtual*/ Base {
public:
Node() = default;
virtual ~Node() = default;
auto check_shared_from_this() const {
// return std::dynamic_pointer_cast<Node const>(shared_from_this());
return std::static_pointer_cast<Node const>(shared_from_this());
}
virtual void check_shared() override { auto as_shared = shared_from_this(); }
virtual void print() override { std::cout << "this is Node" << std::endl; }
private:
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) {
ar& boost::serialization::base_object<Base>(*this);
}
};
BOOST_CLASS_EXPORT(Base)
BOOST_CLASS_EXPORT(Node)
// this is a proxy for a type that owns visitable pointers.
class System {
public:
System(SBase const& p = {}) : _ptr(p) {}
void set(SBase const& n) { _ptr = n; }
void exercise() const { _ptr->check_shared(); }
void print() { _ptr->print(); }
private:
SBase _ptr = nullptr;
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) { ar& _ptr; }
};
BOOST_AUTO_TEST_SUITE(boost_serialization)
BOOST_AUTO_TEST_CASE(repro) {
std::stringstream ss;
{
SBase a = std::make_shared<Node>(), b = std::make_shared<Node>();
b->next(a);
System sys(b);
sys.exercise();
boost::archive::text_oarchive oa(ss);
oa << sys;
}
{
System sys;
{
boost::archive::text_iarchive ia(ss);
ia >> sys;
}
sys.print(); // this call is fine
sys.exercise();
}
}
BOOST_AUTO_TEST_SUITE_END()
Output
输出
Side notes:
- Prefer registering types outside of serialization methods
- Use NVP wrappers consistently or don't if you don't need them anyways
- avoid virtual inheritance if possible: they invite numerous extra subtle sources of undefined behaviour and have a runtime/storage/code size cost
I've taken the liberty to rename things during review - just to aid my understanding of the code's intent.
我在审阅期间冒昧地重命名了一些东西--只是为了帮助我理解代码的意图。
更多回答
Spectacular. This solution demonstrates two necessary actions to solve my problem: factoring enable_shared_from_this
into the base class, and using a pointer cast to get a pointer of correct type at the location I need the pointer of derived type. Thank you! As I implemented this, I also encountered an issue with multiple inheretance, and this SO post offers a solution.
太壮观了。这个解决方案演示了两个必要的操作来解决我的问题:将Enable_Shared_From_This分解到基类中,并使用指针强制转换在我需要派生类型的指针的位置获得正确类型的指针。谢谢!当我实现这一点时,我也遇到了一个多重继承的问题,这篇文章提供了一个解决方案。
我是一名优秀的程序员,十分优秀!