Boost serialization of reference member abstract class - c++

I'm trying to figure out how to serialize a class that I put together with Boost. I'll get right to the code:
#ifndef TEST_H_
#define TEST_H_
#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class Parent
{
public:
int test_val = 1234234;
int p()
{
return 13294;
}
int get_test_val()
{
std::cout << test_val << std::endl;
return test_val;
}
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive &ar, const unsigned int /*version*/)
{
ar &test_val;
}
};
class RefMem : public Parent
{
public:
RefMem()
{
test_val = 12342;
std::cout << test_val << std::endl;
}
};
class Test
{
public:
friend class boost::serialization::access;
int t_;
Parent &parent_;
Test(int t, Parent &&parent = RefMem());
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version){
ar &t_;
ar &parent_;
}
//template<class
};
#endif
#include "test.h"
#include <iostream>
#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
Test :: Test(int t, Parent &&parent) : parent_(parent)
{
std::cout << this->parent_.test_val << std::endl;
t_ = t;
parent_ = parent;
}
int main()
{
Test test = Test(50);
std::cout << "t_: " << test.t_ << std::endl;
std::cout << "Test val: " << test.parent_.get_test_val() << std::endl;
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << test;
}
Test cloned;
std::istringstream iss(oss.str());
{
boost::archive::text_iarchive ia(iss);
ia >> cloned;
}
std::cout << "t_: " << cloned.t_ << std::endl;
std::cout << "Test val: " << cloned.parent_.get_test_val() << std::endl;
}
I'm basically shooting in the dark. I'm new to C++ and I could get a basic example to work, but nothing like this where I serialize a reference member that is a child of an abstract class and then deserialize it. This code is just replicating what I'm trying to do in another program. I have a few random functions/variables just for testing.
Edit: How would I get this code to compile and work properly?

You're confused about the ownership semantics of references.
The reference parent_ merely "points" to an instance of RefMem¹. When you serialize, it's "easy" to write these (because they're lvalue-references, the value itself will have been serialized).
However for deserialization, things are not so simple, simply because we do-not have an instance of MemRef to "point" to. We could expect Boost Serialization to (somehow) dynamically instantiate a MemRef out of thin air and silently make the reference point to it. However, at best this will lead to memory leaks.
There's another thing about reference members specifically. Reference member can only be initialized in the constructor's initializer list.
Because Boost Serialization serializes values it does not construct these objects, and the question is how the reference can even be initialized at all.
Your current constructor has a number of related issues:
Test(int t, Parent && parent = RefMem()) : parent_(parent) {
std::cout << __FUNCTION__ << ":" << this->parent_.test_val << "\n";
t_ = t;
parent_ = parent; // OOPS! TODO FIXME
}
firstly, the constructor disables the compiler-generated default constructor, so that, indeed, the line Test cloned; couldn't even compile
secondly, the default argument for parent is a rvalue-reference and it becomes dangling as soon as the constructor returns. Your program has Undefined Behaviour
Thirdly the line
parent_ = parent; // OOPS! TODO FIXME
doesn't do what you think it does. It copies the value of the Parent object from parent over the object referred to by parent_. This is likely not visible as parent_ and parent are the same object here, but there's even Object Slicing involved (What is object slicing?).
What do?
Best to regroup and hit the documentation for Serialization of References:
Classes that contain reference members will generally require non-default
constructors as references can only be set when an instance is constructed.
The example of the previous section is slightly more complex if the class has
reference members. This raises the question of how and where the objects
being referred to are stored and how are they created. Also there is the
question about references to polymorphic base classes. Basically, these are
the same questions that arise regarding pointers. This is no surprise as
references are really a special kind of pointer.
We address these questions by serializing references as though they were
pointers.
(emphasis mine)
The documentation does go on to suggest load_construct_data/save_construct_data to alleviate the non-default-constructibility of Test.
Note that their suggestion to handle the reference member as a pointer seems nice, but it only makes sense if the actual pointed-to object is also serialized through a pointer in the same archive. In such case Object Tracking will spot the aliasing pointer and avoid creating a duplicate instance.
If not, you'll still have your memory leak, and possibly broken program state.
Demo Using load/save_construct_data
Here's a demo of essentially the technique outlined above. Note that we're leaking the dynamically allocated objects. I don't like this style because it's essentially treating the reference as if it were a pointer.
If that's what we want, we should consider using pointers (see below)
Live On Coliru
#ifndef TEST_H_
#define TEST_H_
#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class Parent {
public:
int test_val = 1234234;
int p() { return 13294; }
int get_test_val() {
std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
return test_val;
}
template <class Archive> void serialize(Archive &ar, unsigned) {
ar & test_val;
}
};
class RefMem : public Parent {
public:
RefMem() {
test_val = 12342;
std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
}
};
class Test {
public:
friend class boost::serialization::access;
int t_;
Parent &parent_;
Test(int t, Parent& parent) : parent_(parent) {
std::cout << __PRETTY_FUNCTION__ << ":" << this->parent_.test_val << "\n";
t_ = t;
}
template <class Archive> void serialize(Archive &ar, const unsigned int file_version) {
ar &t_;
//ar &parent_; // how would this behave? We don't own it... Use pointers
}
// template<class
};
namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(Archive & ar, const Test * t, const unsigned int file_version) {
// save data required to construct instance
ar << t->t_;
// serialize reference to Parent as a pointer
Parent* pparent = &t->parent_;
ar << pparent;
}
template<class Archive>
inline void load_construct_data(Archive & ar, Test * t, const unsigned int file_version) {
// retrieve data from archive required to construct new instance
int m;
ar >> m;
// create and load data through pointer to Parent
// tracking handles issues of duplicates.
Parent * pparent;
ar >> pparent;
// invoke inplace constructor to initialize instance of Test
::new(t)Test(m, *pparent);
}
}}
#endif
#include <iostream>
#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
int main() {
Parent* the_instance = new RefMem;
Test test = Test(50, *the_instance);
std::cout << "t_: " << test.t_ << "\n";
std::cout << "Test val: " << test.parent_.get_test_val() << "\n";
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
Test* p = &test;
oa << the_instance << p; // NOTE SERIALIZE test AS-IF A POINTER
}
{
Parent* the_cloned_instance = nullptr;
Test* cloned = nullptr;
std::istringstream iss(oss.str());
{
boost::archive::text_iarchive ia(iss);
ia >> the_cloned_instance >> cloned;
}
std::cout << "t_: " << cloned->t_ << "\n";
std::cout << "Test val: " << cloned->parent_.get_test_val() << "\n";
std::cout << "Are Parent objects aliasing: " << std::boolalpha <<
(&cloned->parent_ == the_cloned_instance) << "\n";
}
}
Prints
RefMem::RefMem():12342
Test::Test(int, Parent&):12342
t_: 50
int Parent::get_test_val():12342
Test val: 12342
Test::Test(int, Parent&):12342
t_: 50
int Parent::get_test_val():12342
Test val: 12342
Are Parent objects aliasing: true
Alternatively: say what we want
To avoid the leakiness and the usability issues associated with reference members, let's use a shared_ptr instead!
Live On Coliru
#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/make_shared.hpp>
class Parent {
public:
int test_val = 1234234;
int p() { return 13294; }
int get_test_val() {
std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
return test_val;
}
template <class Archive> void serialize(Archive &ar, unsigned) {
ar & test_val;
}
};
class RefMem : public Parent {
public:
RefMem() {
test_val = 12342;
std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
}
};
using ParentRef = boost::shared_ptr<Parent>;
class Test {
public:
int t_ = 0;
ParentRef parent_;
Test() = default;
Test(int t, ParentRef parent) : t_(t), parent_(parent) { }
template <class Archive> void serialize(Archive &ar, const unsigned int file_version) {
ar & t_ & parent_;
}
};
#include <sstream>
int main() {
ParentRef the_instance = boost::make_shared<RefMem>();
Test test = Test(50, the_instance);
std::cout << "t_: " << test.t_ << "\n";
std::cout << "Test val: " << test.parent_->get_test_val() << "\n";
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << the_instance << test; // NOTE SERIALIZE test AS-IF A POINTER
}
{
ParentRef the_cloned_instance;
Test cloned;
std::istringstream iss(oss.str());
{
boost::archive::text_iarchive ia(iss);
ia >> the_cloned_instance >> cloned;
}
std::cout << "t_: " << cloned.t_ << "\n";
std::cout << "Test val: " << cloned.parent_->get_test_val() << "\n";
std::cout << "Are Parent objects aliasing: " << std::boolalpha <<
(cloned.parent_ == the_cloned_instance) << "\n";
}
}
Note that there is no complication anymore. No memory leaks, not even when you don't serialize the RefMem instance separately. And the object tracking works fine with shared pointers (as implemented through boost/serialization/shared_pointer.hpp).
¹ or anything else deriving from Parent, obviously

Related

Disallow serializing an object directly (not through a pointer) if it doesn't have a default constructor

I have recently been using save_construct_data() and load_construct_data() when I need to serialize an object without a default constructor. Since it doesn't make sense to do:
MyObject a; // can't do this because there is no default constructor
archive >> a;
we must do:
MyObject* aPointer;
archive >> a;
which calls load_construct_data() before serialize(). However, of course this only works if the object was serialized using save_constructor_data() which is only called if it is written as a pointer, e.g.
MyObject a(1,2);
MyObject aPointer = &a;
archive << aPointer;
This is all working fine, but it seems like archive << a; works fine, but logically doesn't make sense, as it will never be able to be deserialized. Is there a way to disallow this call so that objects (perhaps class members of a larger class, etc.) don't accidentally write the Object not through a pointer?
------------- EDIT ----------
Attempting to follow SergeyA's suggestion, I have made the following demo. Unfortunately it does not seem to read the data correctly?
#include <fstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
class Point
{
private:
friend class boost::serialization::access;
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
archive & mX;
archive & mY;
}
public:
template<class TArchive>
Point(TArchive& archive)
{
serialize(archive, 0);
}
Point(){} // Only provided to test Works()
Point(const float x, const float y) : mX(x), mY(y) { }
float mX = 4;
float mY = 5;
};
void Works()
{
std::cout << "Works():" << std::endl;
Point p(1,2);
std::ofstream outputStream("test.archive");
boost::archive::text_oarchive outputArchive(outputStream);
outputArchive << p;
outputStream.close();
// read from a text archive
std::ifstream inputStream("test.archive");
boost::archive::text_iarchive inputArchive(inputStream);
Point pointRead;
inputArchive >> pointRead;
std::cout << pointRead.mX << " " << pointRead.mY << std::endl;
}
void DoesntWork()
{
std::cout << "DoesntWork():" << std::endl;
Point p(1,2);
std::ofstream outputStream("test.archive");
boost::archive::text_oarchive outputArchive(outputStream);
outputArchive << p;
outputStream.close();
std::ifstream inputStream("test.archive");
boost::archive::text_iarchive inputArchive(inputStream);
Point pointRead(inputArchive);
std::cout << pointRead.mX << " " << pointRead.mY << std::endl;
}
int main()
{
Works(); // Output "1 2"
DoesntWork(); // Output "0 0"
return 0;
}

boost serialiaze input stream error

I am working on a simple serialization class. I keep throwing an exception on the input stream. I have put together the below example of what I am attempting to accomplish in simple terms.
I have this simple example of boost serialization that I am getting an exception on:
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#define NVP(X) X
class base {
public:
friend class boost::serialization::access;
base (){ v1 = 10;}
int v1;
template<class Archive>
void serialize(Archive & ar, const unsigned int file_version)
{
ar & NVP(v1);
}
virtual void bla()=0;
};
class derived : public base {
public:
friend class boost::serialization::access;
int v2 ;
derived() { v2 = 100;}
template<class Archive>
void serialize(Archive & ar, const unsigned int file_version){
boost::serialization::base_object<base>(* this);
ar & NVP(v2);
}
virtual void bla(){};
};
BOOST_CLASS_EXPORT(base);
BOOST_CLASS_EXPORT_GUID(derived, "derived");
int main ( )
{
std::stringstream ss;
boost::archive::text_oarchive ar(ss);
base *b = new derived();
ar << NVP(b);
std::cout << ss.str()<<std::endl;
std::istringstream ssi;
base *b1 = new derived();
{
boost::archive::text_iarchive ar1(ssi);
ar1 >> b1;
}
//std::cout << ssi.str();
std::cout << "v1: " << b1->v1 << std::endl;
}
The exception that I am getting is:
terminate called after throwing an instance of 'boost::archive::archive_exception'
what(): input stream error
Any help would be appreciated.
You're reading from an empty stream:
std::istringstream ssi;
// ...
boost::archive::text_iarchive ar1(ssi);
Also, you leak this object:
base *b1 = new derived();
Here's a fixed example, notes:
it's very good practice/important to close archives before using the streamed data
BOOST_CLASS_EXPORT_GUID(derived, "derived") doesn't add anything beyond BOOST_CLASS_EXPORT(derived)
you can print the v2 conditionally:
if (auto* d = dynamic_cast<derived*>(b1))
std::cout << "v2: " << d->v2 << std::endl;
I've used bla() as an example to print the values instead
NVP() is a bit iffy there. Why not just leave it out for non-tagged archives (ie. other than XML)? If you intend to support XML, just use BOOST_SERIALIZATION_NVP, boost::serialization::make_nvp etc.
std::cout << "v2: " << b1->v2 << std::endl; was completely out of place
just initialize b1 to null so you don't leak it; remember to free all pointers (use smart pointers!)
the mix of public: and friend in your types didn't really mean much
Live On Coliru
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <sstream>
class base {
public:
base(int v1) : v1(v1) {}
virtual void bla() const = 0;
private:
friend class boost::serialization::access;
template <class Archive> void serialize(Archive &ar, unsigned /*int const file_version*/) {
ar & BOOST_SERIALIZATION_NVP(v1);
}
protected:
int v1;
};
class derived : public base {
public:
derived(int v1 = 10, int v2 = 100) : base(v1), v2(v2) {}
virtual void bla() const {
std::cout << "v1: " << v1 << ", v2: " << v2 << "\n";
}
private:
friend class boost::serialization::access;
int v2;
template <class Archive> void serialize(Archive &ar, unsigned /*int const file_version*/) {
boost::serialization::base_object<base>(*this);
ar & BOOST_SERIALIZATION_NVP(v2);
}
};
BOOST_CLASS_EXPORT(base)
BOOST_CLASS_EXPORT(derived)
int main() {
std::stringstream ss;
{
boost::archive::text_oarchive ar(ss);
base *b = new derived();
ar << boost::serialization::make_nvp("base", b);
delete b; // TODO use RAII instead
}
std::cout << ss.str() << std::endl;
base *deserialized = nullptr;
{
boost::archive::text_iarchive ar1(ss);
ar1 >> boost::serialization::make_nvp("base", deserialized);
}
deserialized->bla();
delete deserialized;
}
Prints
22 serialization::archive 12 0 7 derived 1 0
0 100
v1: 10, v2: 100

boost serialize polymorphic class

With the following example I attempting to learn a few new to me concepts.
abstraction
polymorphic classes
factory programming.
boost serialization
The nuances of how pointers behave are still something I am working to figure out.
Here is a small program that I have written to show you the issue I am struggling to understand.
When I unserialize the polymorphic object below I only get an object created from the default constructor.
TodoFactory::retrieveATodo is not recreating the object from the serialized data. This is displayed by the output of "unserialzed command" in that function.
Here is the full program:
#include <string>
#include <bitset>
#include <boost/serialization/string.hpp>
#include <sstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
//abstract class
class aTodo{
private:
friend class boost::serialization::access;
protected:
const char _initType;
public:
aTodo():_initType(0x00){};
aTodo(const char type):_initType(type){};
std::string oarchive(){
std::ostringstream archive_stream;
{
boost::archive::text_oarchive archive(archive_stream);
archive << *this;
}
archive_stream.flush();
std::string outbound_data=archive_stream.str();
std::string foutbound_data;
foutbound_data=_initType;
foutbound_data+=outbound_data;
std::cout << "length: " << foutbound_data.length() << std::endl;
return foutbound_data;
}
virtual void Do()=0;
virtual ~aTodo(){};
template<class Archive>
void serialize(Archive & ar, unsigned int version){
ar & _initType;
};
char getInitType(){return _initType;};
};
// include headers that implement a archive in simple text format
class todoExec:public aTodo{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(
Archive& ar,
unsigned int version
)
{
std::cout << "serialize todoexec" << std::endl;
//base
boost::serialization::base_object<aTodo>(*this);
//derived
ar & _command;
}
std::string _command;
protected:
public:
static const char _TYPE=0x01;
todoExec():aTodo(_TYPE){};
todoExec(std::string command):aTodo(_TYPE){_command=command;};
void Do(){std::cout << "foo" << std::endl;};
virtual ~todoExec(){};
std::string getCommand(){return _command;};
};
class todoFactory{
private:
protected:
public:
std::unique_ptr<aTodo> retrieveAtodo(const std::string & total){
std::cout << "here" << std::endl;
char type=total.at(0);
std::cout << "bitset: " << std::bitset<8>(type) << std::endl;
std::string remainder=total.substr(1);
if(type==0x01){
std::cout << "remainder in retrieve: " << remainder << std::endl;
std::unique_ptr<todoExec> tmp(new todoExec());
std::stringstream archive_stream(remainder);
std::cout << "stream remainder: " << archive_stream.str() << std::endl;
{
boost::archive::text_iarchive archive(archive_stream);
archive >> *tmp;
}
std::cout << "unserialized type: " << std::bitset<8>(tmp->getInitType()) << std::endl;
std::cout << "unserialized command: " << tmp->getCommand() << std::endl;
return std::move(tmp);
}
};
std::unique_ptr<aTodo> createAtodo(char type,std::string command){
if(type==0x01){
std::unique_ptr<todoExec> tmp(new todoExec(command));
return std::move(tmp);
}
};
};
int main(){
char mtype=0x01;
std::string dataToSend = "ls -al /home/ajonen";
std::unique_ptr<todoFactory> tmpTodoFactory; //create factory
std::unique_ptr<aTodo> anExecTodo=tmpTodoFactory->createAtodo(mtype,dataToSend); //create ExecTodo from factory
if(auto* m = dynamic_cast<todoExec*>(anExecTodo.get()))
std::cout << "command to serialize: " << m->getCommand() << std::endl;
//archive
std::string remainder = anExecTodo->oarchive();
//now read in results that are sent back
std::unique_ptr<aTodo> theResult;
theResult=tmpTodoFactory->retrieveAtodo(remainder);
std::cout << "resultant type: " << std::bitset<8>(theResult->getInitType()) <<std::endl;
if(auto* d = dynamic_cast<todoExec*>(theResult.get()))
std::cout << "resultant Command: " << d->getCommand() <<std::endl;
return 0;
}
And here is the program output:
command to serialize: ls -al /home/ajonen
length: 36
here
bitset: 00000001
remainder in retrieve: 22 serialization::archive 12 0 0 1
stream remainder: 22 serialization::archive 12 0 0 1
serialize todoexec
unserialized type: 00000001
unserialized command:
resultant type: 00000001
resultant Command:
I also found out that the serialize method is only being called for the base class aTodo. I am going to need to find a way to make that virtual, but it is a template function. That is problem number one.
Your program has Undefined Behaviour because all of the factory functions have missing returns.
Next up, using a type code in a class hierarchy is a Design Smell.
Concrete hints:
serialize the same type as you deserialize
let Boost Serialization handle the polymorphism (otherwise, why do you use polymorphism, or why do you use Boost Serialization?). Boost handles it when you serialize (smart) pointers to base.
register your classes (BOOST_CLASS_EXPORT). You had included the header but didn't use it.
There doesn't seem to be a reason for the factory. Consider dropping it
In general, remove cruft. it's hard to think when your code is too noisy. Here's my cleaned up version:
Live On Coliru
This also uses Boost for streaming to string without unnecessary copying.
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
namespace Todo
{
struct BaseTodo {
using Ptr = std::unique_ptr<BaseTodo>;
virtual ~BaseTodo() = default;
virtual void Do() = 0;
virtual unsigned getInitType() { return 0x00; };
private:
friend class boost::serialization::access;
template <class Ar> void serialize(Ar &, unsigned) {}
};
class Exec : public BaseTodo {
public:
Exec(std::string const &command = "") : _command(command){};
virtual unsigned getInitType() { return 0x01; };
virtual void Do() { std::cout << "foo: " << getCommand() << std::endl; };
std::string getCommand() const { return _command; };
private:
friend class boost::serialization::access;
template <class Archive> void serialize(Archive &ar, unsigned) {
boost::serialization::base_object<BaseTodo>(*this);
ar &_command;
}
std::string _command;
};
}
//BOOST_CLASS_EXPORT(BaseTodo)
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Todo::BaseTodo)
BOOST_CLASS_EXPORT(Todo::Exec)
namespace Todo
{
class Factory {
Factory() = default;
public:
using Ptr = BaseTodo::Ptr;
using FactoryPtr = std::shared_ptr<Factory>;
static FactoryPtr create() { return FactoryPtr(new Factory); }
static std::string save(Ptr todo) {
std::string out;
{
namespace io = boost::iostreams;
io::stream<io::back_insert_device<std::string> > os(out);
boost::archive::text_oarchive archive(os);
archive << todo;
}
return out;
}
static Ptr load(std::string const &s) {
Ptr p;
{
namespace io = boost::iostreams;
io::stream<io::array_source> is(io::array_source{ s.data(), s.size() });
boost::archive::text_iarchive archive(is);
archive >> p;
}
return std::move(p);
}
Ptr createExec(std::string command) { return BaseTodo::Ptr(new Exec(command)); }
};
}
int main() {
auto factory = Todo::Factory::create();
// ROUNDTRIP save,load
auto todo = factory->load(
factory->save(
factory->createExec("ls -al /home/ajonen")
)
);
std::cout << "Type: " << std::hex << std::showbase << todo->getInitType() << std::endl;
todo->Do();
}
Here's another take without virtuals, inheritance and dynamic allocations:
Live On Coliru
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/variant.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
namespace Todo
{
struct None {
void Do() const {};
template <class Ar> void serialize(Ar&, unsigned) {}
};
class Exec {
public:
Exec(std::string const &command = "") : _command(command){};
void Do() const { std::cout << "foo: " << getCommand() << std::endl; };
std::string getCommand() const { return _command; };
private:
friend class boost::serialization::access;
template <class Ar> void serialize(Ar &ar, unsigned) {
ar &_command;
}
std::string _command;
};
using Todo = boost::variant<None, Exec>;
struct Factory {
static std::string save(Todo const& todo) {
std::string out;
{
namespace io = boost::iostreams;
io::stream<io::back_insert_device<std::string> > os(out);
boost::archive::text_oarchive archive(os);
archive << todo;
}
return out;
}
static Todo load(std::string const &s) {
Todo todo;
{
namespace io = boost::iostreams;
io::stream<io::array_source> is(io::array_source{ s.data(), s.size() });
boost::archive::text_iarchive archive(is);
archive >> todo;
}
return std::move(todo);
}
};
}
namespace visitors {
namespace detail {
template <typename F> struct internal_vis : boost::static_visitor<void> {
internal_vis(F& f) : _f(f) {}
template <typename... T>
void operator()(T&&... a) const { return _f(std::forward<T>(a)...); }
private:
F& _f;
};
}
template <typename F, typename V>
void apply(F const& f, V const& v) { return boost::apply_visitor(detail::internal_vis<F const>(f), v); }
template <typename F, typename V>
void apply(F const& f, V& v) { return boost::apply_visitor(detail::internal_vis<F const>(f), v); }
}
namespace Todo { namespace Actions { template <typename T>
void Do(T const& todo) {
visitors::apply([](auto const& cmd) { cmd.Do(); }, todo);
}
} }
int main() {
using namespace Todo;
Factory factory;
// ROUNDTRIP save,load
auto todo = factory.load(
factory.save(
Exec("ls -al /home/ajonen")
)
);
std::cout << "Type: " << std::hex << std::showbase << todo.which() << std::endl;
Actions::Do(todo);
}
Prints
Type: 0x1
foo: ls -al /home/ajonen

Calling std::function object pointing to the method of deallocated object

Consider this code:
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;
typedef function<void(const int&)> SomeFunc;
class X {
public:
X(string name):name_(name)
{ cout << "ctor " << name_ << endl; }
~X()
{
cout << "dtor " << name_ << endl;
name_ = "empty";
}
SomeFunc
getSomeFunc()
{ return bind(&X::someMethod, this, _1); }
private:
string name_;
void
someMethod(const int& a)
{
cout << name_ << " some method with " << a << endl;
}
};
int main()
{
SomeFunc f;
{
shared_ptr<X> x(new X("Object"));
f = x->getSomeFunc();
f(1);
}
f(2);
return 0;
}
Sometimes, output gives me this:
ctor Object
Object some method with 1
dtor Object
empty some method with 2
other times this:
ctor Object
Object some method with 1
dtor Object
some method with 2
In real world, it would most probably give me crashes once deallocated object tries to access it's attributes.
So here is a question - as function does not guarantee holding a reference to the object which method it's pointing to, what is the best practice to avoid crashes when function is called after referenced object was already deallocated?
One of the solutions I might think of - maintain a special flag bool deallocated_ inside object and check it inside the method which might be called after deallocation. However, I suspect, it's not reliable either.
UPDATE (from comments):
The real reason I need this workaround is the library that takes function as a parameter. This library operates asynchronously and I have no control over function objects passed into it. That's why when my object is deallocated, library still can invoke callbacks using originally passed function which leads to a crash.
Your object is being held by a shared_ptr, so you can use a lambda to close over the shared_ptr:
auto func = [ptr](const int &p){ ptr->someMethod(p); };
You'll need to use shared_from_this to get ptr within the class.
Here's a full example that works:
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
using namespace std::placeholders;
typedef function<void(const int&)> SomeFunc;
class X : public enable_shared_from_this<X> {
public:
X(string name) : name_(name) {
cout << "ctor " << name_ << endl;
}
~X() {
cout << "dtor " << name_ << endl;
name_ = "empty";
}
SomeFunc getSomeFunc() {
auto ptr = shared_from_this();
return [ptr](const int &a){ ptr->someMethod(a); };
}
private:
string name_;
void someMethod(const int& a) {
cout << name_ << " some method with " << a << endl;
}
};
int main()
{
SomeFunc f;
{
shared_ptr<X> x(new X("Object"));
f = x->getSomeFunc();
f(1);
}
f(2);
return 0;
}
The output looks like this:
ctor Object
Object some method with 1
Object some method with 2
dtor Object
Sulution 1) Using weak_ptr + lambda (almost the same as from b4hand, but it won't force your class beeing alive)
Inherit your class from std::enable_shared_from_this
class X : public enable_shared_from_this<X>
and change getSomeFunc to something like this:
SomeFunc getSomeFunc()
{
weak_ptr<X> weak = shared_from_this();
return [weak, this](const int& a){
shared_ptr<X> shared = weak.lock();
if (shared)
{
this->someMethod(a);
}
};
}
output:
ctor Object
Object some method with 1
dtor Object
more details here and here.
Solution 2) A bit of crazy code + lambda
If you can't or don't want to use shared/weak ptrs, you can do it this way:
#include <memory>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <set>
using namespace std;
typedef function<void(const int&)> SomeFunc;
class X {
private:
static set<X*> _aliveInstanties;
public:
X(string name) :name_(name)
{
_aliveInstanties.insert(this);
cout << "ctor " << name_ << endl;
}
~X()
{
_aliveInstanties.erase(_aliveInstanties.find(this));
cout << "dtor " << name_ << endl;
name_ = "empty";
}
SomeFunc getSomeFunc()
{
return [this](const int& a)
{
if (_aliveInstanties.find(this) != _aliveInstanties.end())
{
this->someMethod(a);
}
};
}
private:
string name_;
void someMethod(const int& a)
{
cout << name_ << " some method with " << a << endl;
}
};
You can create a class that holds a function pointer and a shared_ptr to the object. The shared_ptr to the object guarantees the object won't be destroyed until your function class is destroyed.
Another solution without using lambda is to derive from enable_shared_from_this and pass shared_from_this in getSomeFunc method:
class X : public enable_shared_from_this<X> {
public:
X(string name):name_(name)
{ cout << "ctor " << name_ << endl; }
~X()
{
cout << "dtor " << name_ << endl;
name_ = "empty";
}
SomeFunc
getSomeFunc()
{
return bind(&X::someMethod, shared_from_this(), _1);
}
private:
string name_;
void
someMethod(const int& a)
{
cout << name_ << " some method with " << a << endl;
}
};
This, however, will hold object until all callbacks are released.

How to define a global variable template in C++?

Suppose you have to write a template library that operates on a client-supplied type. Also, suppose that this library really needs to access a global variable parameterized by the client-supplied type. How would you go about implementing this pattern?
#AnonymousCoward
this is derived from your solution. note the initialization/destruction patterns in this variant (read the output if you don't have it memorized already). you can use this to defer creation until access, and to destruct and free at termination (which may be useful for custom types):
#include <iostream>
#include <memory>
class t_custom {
public:
t_custom() {
std::cout << "custom ctor: " << __PRETTY_FUNCTION__ << "\n";
}
~t_custom() {
std::cout << "custom dtor: " << __PRETTY_FUNCTION__ << "\n";
}
};
template<typename T>
class Global {
public:
static T* Shared() {
std::cout << "enter: " << __PRETTY_FUNCTION__ << "\n";
static std::auto_ptr<T>pVar(new T);
std::cout << "access: " << __PRETTY_FUNCTION__ << ":\t\t";
return pVar.get();
}
private:
Global();
~Global();
Global(const Global& rhs);
Global& operator=(const Global& rhs);
};
template<typename T>
class Global_orig {
public:
static T* const pVar;
private:
Global_orig();
Global_orig(const Global_orig& rhs);
Global_orig& operator=(const Global_orig& rhs);
};
template<typename T>T* const Global_orig<T>::pVar(new T); // << oh no! global construction
int main(int argc, char* const argv[]) {
std::cout << ">> main: " << __PRETTY_FUNCTION__ << "\n\n";
std::cout << Global<float>::Shared() << "\n";
std::cout << Global<int>::Shared() << "\n";
std::cout << Global<t_custom>::Shared() << "\n";
std::cout << Global_orig<t_custom>::pVar << "\n";
std::cout << "\n<< main: " << __PRETTY_FUNCTION__ << "\n\n";
return 0;
}
it may also be a good idea for the client to supply a factory functor for you, rather than forcing them to use T's default initializer.
Here is the solution I use to emulate this behavior:
template <typename T> class Global {
public:
static T *pVar;
private:
Global() {}
Global(const Global& rhs) {}
void operator=(const Global& rhs) {}
};
template <typename T> T* Global<T>::pVar = new T;
It seems to do what I want for my particular application. Is there any problem that restricts its applicability?