I have lazy singleton class, that needs to be serialized using boost on the first call.
Header file:
class Singleton
{
public:
static Singleton& Instance()
{
static Singleton theSingleInstance;
return theSingleInstance;
}
void load();
private:
Singleton();
Singleton(const Singleton& root);
Singleton& operator=(const Singleton&);
std::map<std::string,std::string > m_desc;
friend class boost::serialization::access;
template<typename Archive>
void serialize(Archive& arc, const unsigned int version)
{
arc & BOOST_SERIALIZATION_NVP(m_desc);
}
const char* FILENAME = "./config.xml";
};
Source file
#include "singleton.h"
Singleton::Singleton()
{
load();
}
void Singleton::load()
{
try
{
std::ifstream f(FILENAME);
boost::archive::xml_iarchive arc(f);
arc & boost::serialization::make_nvp("Config",Instance());
}
catch(...)
{
std::cout << "Exception" << std::endl;
}
}
So when I try to start my code using this singleton, it hangs. With the debugger I can see that it does not go to load() method many times (and it's ok). When I pause the debugger, it stops on the line return theSingleInstance;, but it does not go through the breakpoint on this line many times also. What am I doing wrong?
You call load from inside the constructor. Which means that you're in the constructor of your static theSingleInstance when you... call Instance
:
#0 in Singleton::load at test.cpp <test.cpp>
#1 in Singleton::Singleton at test.cpp <test.cpp>
#2 in Singleton::Instance at test.cpp <test.cpp>
#3 in main at test.cpp <test.cpp>
Since construction c++11 function-local statics is guaranteed to be thread safe, which means - in your implementation - that execution will block until the instance is fully constructed (or construction failed, so it can be retried).
Of course, that will never happen because construction is waiting for itself.
0x00000000004030f5 <+629>: callq 0x402be0 <__cxa_guard_acquire#plt>
=> 0x00000000004030fa <+634>: test %eax,%eax
0x00000000004030fc <+636>: je 0x402ff1 <Singleton::load()+369>
Of course this is fixed by not using the external accessor while still constructing the instance, as you already found out.
You might want to consider the practice of giving singletons value-semantics - in case one day you don't want it to be a singleton any more and you want to avoid refactoring.
The other advantage is that it provides full encapsulation:
for example:
// config.h - extremely sparse interface
#include <string>
struct config
{
/// Return a named value from the program's configuration
/// #param name is the name of the required parameter
/// #post the config file shall be cached
/// #return the value if it exists
/// #except std::invalid_argument if the value does not exist
/// #except other exceptions if the config file does not load
///
const std::string& value(const std::string& name) const;
private:
struct impl;
static impl& get();
};
// config.cpp - the implementation
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/map.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <fstream>
#include <map>
struct config::impl
{
impl()
{
std::ifstream f(FILENAME);
boost::archive::xml_iarchive arc(f);
arc & boost::serialization::make_nvp("Config", *this);
}
std::map<std::string,std::string > m_desc;
friend class boost::serialization::access;
template<typename Archive>
void serialize(Archive& arc, const unsigned int version)
{
arc & BOOST_SERIALIZATION_NVP(m_desc);
}
static constexpr const char* FILENAME = "./config.xml";
};
config::impl& config::get() {
static impl _;
return _;
}
const std::string& config::value(const std::string& name) const
{
return get().m_desc.at(name);
}
// demo.cpp
// note - config is copyable, and this has almost no cost
void do_something_with(config cfg)
{
}
struct no_config {}; // some other config source?
// now I can switch on config type at compile time - code can be
// more generic
void do_something_with(no_config)
{
}
int main()
{
// single-use
std::string my_value = config().value("foo");
// pass my config source to a function
do_something_with(config());
// a similar function that uses a different config source
do_something_with(no_config());
return 0;
}
The answer was simple: to replace this line
arc & boost::serialization::make_nvp("Config",Instance());
with
arc & boost::serialization::make_nvp("Config",*this);
Related
I'm trying to implement a non-blocking serial communication in my C++ app. A thread is responsible to do serial communication, and I've written a ThreadSafeClass to exchange data between serial thread and main thread. Here is the core of my code:
main.cpp
#include "serial.hpp"
#include "tsqueue.hpp"
int main(int argc, char *argv[])
{
serial::init();
while (true)
{
fgets(s);
serial::outQueue.enqueue(std::string(s));
}
serial::shutdown();
return 0;
}
tsqueue.hpp
#include <mutex>
#include <queue>
namespace tsqueue
{
template <typename T>
class ThreadSafeQueue
{
private:
mutable std::mutex _mtx;
std::queue<T> _que;
public:
ThreadSafeQueue();
~ThreadSafeQueue();
void enqueue(const T &item);
T tryDequeue(const T &defaultValue, bool &done);
void clear();
bool isEmpty() const;
};
template <typename T>
ThreadSafeQueue<T>::ThreadSafeQueue() {}
template <typename T>
ThreadSafeQueue<T>::~ThreadSafeQueue() { clear(); }
template <typename T>
void tsqueue::ThreadSafeQueue<T>::enqueue(const T &item)
{
std::lock_guard<std::mutex> lock(_mtx);
_que.push(item);
}
template <typename T>
T tsqueue::ThreadSafeQueue<T>::tryDequeue(const T &defaultValue, bool &done)
{
std::lock_guard<std::mutex> lock(_mtx);
if (_que.empty())
{
done = false;
return defaultValue;
}
else
{
T item = _que.front();
_que.pop();
done = true;
return item;
}
}
} // namespace tsqueue
And serial declaration/definition,
serial.hpp
#include <string>
#include "tsqueue.hpp"
namespace serial
{
static tsqueue::ThreadSafeQueue<std::string> inQueue;
static tsqueue::ThreadSafeQueue<std::string> outQueue;
void init();
void shutdown();
}
serial.cpp
#include <string>
#include "serial.hpp"
#include "tsqueue.hpp"
static std::thread _thread;
void run()
{
while (true)
{
std::string str = serial::outQueue.tryDequeue(emptyStr, dequeued);
if (dequeued) { /* Do send 'str' */ }
if (terminationRequested) { break; }
// Some sleep
}
}
void serial::init()
{
serial::inQueue.clear();
serial::outQueue.clear();
_thread = std::thread(run);
}
void serial::shutdown()
{
if (_thread.joinable()) { _thread.join(); }
}
The problem is, when tryDequeue(...) is called by serial thread's run() in serial.cpp, it always sees empty outQueue. However while loop still sees outQueue in main.cpp with provided data, even at later times. I've find out that using debug tools of vscode. I'm new to C++, but experienced in other languages. What am I doing wrong in above code? Do run() and main() see different objects?
Compiler: g++ 7.3.0, Environment: Linux (Ubuntu 18.04)
Edit: If I remove static from definitions of inQueue and outQueue, I get multiple definition error by linker for both. Although I have appropriate include guards.
(Heavily edited after all the non-issues have been repaired and after I finally spotted what was the actual problem:)
The problem is that you have two completely separate instances of outQueue: One in main.o and one in serial.o (or .obj if you are on Windows). The problem is that you declare these as static in a header. That results in individual copies of this in every *.cpp/object which included this header.
Ideally outQueue would not be a global variable. Assuming it should be a global variable you can fix this like this:
serial.hpp
namespace serial
{
// This is a declaration of a global variable. It is harmless
// to include this everywhere.
extern tsqueue::ThreadSafeQueue<std::string> inQueue;
extern tsqueue::ThreadSafeQueue<std::string> outQueue;
}
serial.cpp
namespace serial
{
// This is the actual definition of the variables.
// Without this you get unresolved symbols during link time
// (but no error during compile time). If you have this in
// two *.cpp files you will get multiple definition linker
// errors (but no error at compile time). This must not be
// static because we want all other objects to see this as well.
tsqueue::ThreadSafeQueue<std::string> inQueue;
tsqueue::ThreadSafeQueue<std::string> outQueue;
}
The ThreadSafeQueue itself looks ok to me.
I am building an application with Qt5. My program builds and runs fine, but there is a collision between two threads accessing a data structure. I have a QList of CanMessage objects, and I want to protect some data inside of it using a QMutex. However, as soon as I add the QMutex to my class definition, I get errors:
QList.h: `error: C2280:
'CanMessage::CanMessage(const CanMessage &)': attempting to reference a deleted function`.
Here is my canmessage.h file:
#ifndef CANMESSAGE_H
#define CANMESSAGE_H
#include <QObject>
#include <QMutex>
#include "cansignal.h"
class CanMessage
{
public:
CanMessage();
/* snip - public function prototypes */
private:
/* snip - private prototypes and data */
QMutex m_messageMutex;
};
#endif // CANMESSAGE_H
And cansignal.h:
#ifndef CANSIGNAL_H
#define CANSIGNAL_H
#include <QObject>
#include <QDebug>
#include <QByteArray>
class CanSignal
{
public:
CanSignal();
CanSignal(QString &signalName, quint8 &signalLength, quint8 &signalStartBit,
float &offset, float &factor, bool &isBigEndian, bool &isFloat, bool &isSigned)
{
this->m_name = signalName;
this->m_length = signalLength;
this->m_startBit = signalStartBit;
this->m_offset = offset;
this->m_factor = factor;
this->m_isBigEndian = isBigEndian;
this->m_isFloat = isFloat;
this->m_isSigned = isSigned;
}
bool setName(QString &signalName);
bool setBitLength(quint8 &length);
bool setStartBit(quint8 &startBit);
bool setOffset(float &offset);
bool setFactor(float &factor);
bool setEndianess(bool &isBigEndian);
bool setIsFloat(bool &isFloat);
bool setIsSigned(bool &isSigned);
void setValid();
void setInvalid();
void setEngineeringData(float data);
QString getName();
quint8 getBitLength();
quint8 getStartBit();
float getOffset();
float getFactor();
float getData();
bool isBigEndian();
bool isFloat();
bool isSigned();
bool getSignalValidity();
private:
QString m_name;
quint8 m_length;
quint8 m_startBit;
float m_offset;
float m_factor;
float m_data_float = 0;
bool m_isBigEndian;
bool m_isFloat;
bool m_isSigned;
// Set After everything in signal is filled
bool m_isSignalValid = false;
};
#endif // CANSIGNAL_H
CanMessage::CanMessage(const CanMessage &)
is the copy constructor, obviously being used to place an item into the list. That's not going to work since QMutex is not actually copyable.
How you solve it depends on a number of things. Perhaps the easiest method would be to modify CanMessage so that it has a dynamic mutex (created in the constructor, of course).
Then have a copy constructor for it that first locks the source object mutex then dynamically allocates a new mutex in the target object.
That way, you can guarantee the old object will be "clean" when copying (because you have its mutex) and there'll be no "trying to copy an uncopyable member" problem since the mutex itself is not copied. See footnote (a) for details.
The following code, which is a complete simple snippet showing the problem, compiles okay provided you leave the QMutex m_mutex; line commented out:
#include <QList>
#include <QMutex>
#include <iostream>
class Xyzzy {
private:
int m_xyzzy;
//QMutex m_mutex;
public:
Xyzzy() : m_xyzzy(0) {};
Xyzzy(int val) : m_xyzzy(val) {};
};
int main() {
QList<Xyzzy> myList;
Xyzzy plugh;
myList.push_back(plugh);
return 0;
}
Once you un-comment that line, the compiler rightly complains:
error: use of deleted function 'Xyzzy::Xyzzy(const Xyzzy&)'
(a) In terms of fixing the problem, you could do something like:
#include <QList>
#include <QMutex>
#include <iostream>
class Xyzzy {
private:
int m_xyzzy;
QMutex *m_mutex; // Now a pointer
public:
Xyzzy() : m_xyzzy(0) {
m_mutex = new QMutex(); // Need to create in constructor.
std::cout << "constructor " << m_mutex << '\n';
};
~Xyzzy() {
std::cout << "destructor " << m_mutex << '\n';
delete m_mutex; // Need to delete in destructor.
}
Xyzzy(const Xyzzy &old) {
old.m_mutex->lock();
m_mutex = new QMutex(); // Need to make new one here.
std::cout << "copy constructor from " << old.m_mutex
<< " to " << m_mutex << '\n';
old.m_mutex->unlock();
}
};
int main() {
QList<Xyzzy> myList;
Xyzzy plugh;
myList.push_back(plugh);
return 0;
}
That one works properly, as per the following test run:
constructor 0x21c9e50
copy constructor from 0x21c9e50 to 0x2fff2f0
destructor 0x21c9e50
destructor 0x2fff2f0
In real code, I'd probably opt for smart pointers rather than raw new/delete calls but this is only meant to illustrate the concept. In addition, you'd need to handle all other possibilities which create a new object from an existing one as per the rule of three/five/whatever-comes-next, currently (from memory) limited to the copy assignment member Xyzzy &operator=(const Xyzzy &old).
I have several c++ programs that are all reading a YAML configuration file in /etc/foo/config.yml. I have written a function that reads the config from the file
YAML::Node load_config();
(using the yaml-cpp library).
I would like this configuration to be loaded once, at the beginning of the main() function of my program, and then accessible everywhere as some kind of global variable.
Currently, many of my functions have extra parameters that are just values read from the configuration file. It could be avoided by having this global configuration, making my function definitions and calls much simpler and readable.
Side note: I am also using OpenMP for distributing computation, which means that the configuration must be accessible to all parallel processes.
Could someone give a tiny example of what this would look like when done the right way?
Thanks!
here's one way. It's a variation on the idea of the schwartz counter to manage a global singleton (for example, std::cout itself)
// globals.hpp
#include <istream>
struct globals_object
{
globals_object()
{
// record number of source files which instanciate a globals_object
++init_count_;
}
~globals_object()
{
// The last source file cleans up at program exit
if(--init_count_ == 0)
{
if (pimpl_)
{
delete pimpl_;
}
}
}
// internal implementation
struct impl
{
void load(std::istream& is)
{
// do loading code here
}
int get_param_a() const {
return a_;
}
int a_;
};
// (re)load global state
void load(std::istream&& is)
{
if (pimpl_) delete pimpl_;
pimpl_ = new impl;
pimpl_->load(is);
}
// public parameter accessor
int get_param_a() const {
return get_impl().get_param_a();
}
private:
static int init_count_;
static impl* pimpl_;
static impl& get_impl()
{
return *pimpl_;
}
};
// one of these per translation unit
static globals_object globals;
// globals.cpp
// note - not initialised - will be zero-initialised
// before global constructors are called
// you need one of these in a cpp file
int globals_object::init_count_;
globals_object::impl* globals_object::pimpl_;
// main file
// #include "globals.hpp"
#include <fstream>
int main()
{
globals.load(std::ifstream("settings.yml"));
}
// any other file
// #include "globals.hpp"
#include <iostream>
void foo()
{
std::cout << globals.get_param_a() << std::endl;
}
I have Application singleton wich has method
void addHandler(const std::string& command, std::function<std::string (const std::string&)> handler)
I want to create a lot of cpp files with handlers like this
//create_user_handler.cpp
Application::getInstance()->addHandler("create_user", [](std::string name) {
UserPtr user = User::create(name);
return user->toJson();
});
How automatically call this from my cpp files?
I try to change from void addHandler to bool addHandler and than use
namespace {
bool b = Application::getInatance()->addHandler......
}
but it didn't work for me
Udate
It works now, but could it be done in a better way, without unused bool variable?
Make use of static class instantiation.
Pseudo code -
Add a registrator class.
class Registrator {
template <typename Func>
Registrator(const std::string& name, Func handler) {
Application::getInstance()->addHandler(name, handler);
}
};
And in each cpp file, create a static class object:
test.cpp
static Registrator test_cpp_reg("create_user", [](std::string name) {
UserPtr user = User::create(name);
return user->toJson();
});
I assume that addHandler() should return bool? Otherwise, you can't assign to the bool variable.
To remove the bool return of addHandler, make the call from the constructor of some other class that you in turn instantiate statically.
This kind of code can work, but it is tricky. The problem is that in C/C++, the order of static-storage initializers is undefined. So while a static initializer is allowed to call any code, if that code references as-yet-uninitialized data, it will fail. And unfortunately the failure is non-deterministic. It might work for a while, and then you change some compiler flag or module order, and splat!
One trick is to implement the instance state of getInstance() using a dumb pointer, because that is always initialized to zero (null) before any of the static initializers fire. For example, the following code will print "Added foo" before main starts:
#include <string>
#include <functional>
#include <map>
#include <iostream>
class Application {
public:
static Application* getInstance() {
// Not thread-safe!
if (instance == 0) {
instance = new Application;
}
return instance;
}
typedef std::function<std::string(const std::string&)> HANDLER;
typedef std::map<std::string, HANDLER> HANDLER_MAP;
bool addHandler(const std::string& command, HANDLER handler) {
handlerMap.insert(HANDLER_MAP::value_type(command, handler));
std::cout << "Added " << command << "\n";
return true;
}
HANDLER_MAP handlerMap;
static Application* instance;
};
Application* Application::instance;
std::string myHandler(const std::string&) { return ""; }
bool b = Application::getInstance()->addHandler("foo", myHandler);
int main()
{
return 0;
}
Please help me deserialize a derived class to base-class pointer. I attach the complete source code example.
request.hpp (no pair cpp file)
#ifndef REQUEST_HPP
#define REQUEST_HPP
#include <memory>
#include <string>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
namespace demo {
namespace common {
class request {
public:
static const int INVALID_ID = -42;
request()
: id_(INVALID_ID), timestamp_(0), source_ip_("unknown") {};
request(int id, long timestamp, const std::string& source_ip)
: id_(id), timestamp_(timestamp), source_ip_(source_ip) {};
virtual ~request() {};
int id() const { return id_; }
long timestamp() const { return timestamp_; }
std::string source_ip() const { return source_ip_; }
protected:
int id_;
long timestamp_;
std::string source_ip_;
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned version) {
ar & BOOST_SERIALIZATION_NVP(id_);
ar & BOOST_SERIALIZATION_NVP(timestamp_);
ar & BOOST_SERIALIZATION_NVP(source_ip_);
}
};
typedef std::shared_ptr<request> request_ptr;
}
};
#endif
command.hpp (derived class)
#ifndef COMMAND_HPP
#define COMMAND_HPP
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include <demo/common/request.hpp>
namespace demo {
namespace common {
class command : public request {
public:
command(): name_("untitled") {};
explicit command(const std::string& name) : name_(name) {};
virtual ~command() {};
virtual void execute();
std::string name() const { return name_; }
protected:
std::string name_;
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned version) {
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(request);
ar & BOOST_SERIALIZATION_NVP(name_);
}
};
typedef std::shared_ptr<command> command_ptr;
}
};
BOOST_CLASS_EXPORT_KEY(demo::common::command)
#endif
command.cpp
#include "command.hpp"
#include <iostream>
BOOST_CLASS_EXPORT_IMPLEMENT(demo::common::command)
namespace demo {
namespace common {
void command::execute() {
std::cout << " I am '" + name_ +"' and I am executing..." << std::endl;
}
}
};
serializer.hpp
#ifndef SERIALIZER_HPP
#define SERIALIZER_HPP
#include <sstream>
#include <string>
/* classes to serialize */
#include <demo/common/request.hpp>
#include <demo/common/command.hpp>
namespace demo {
namespace common {
class serializer {
public:
serializer() : {};
template<typename T>
std::string serialize(const T& t){
std::stringstream stream;
boost::archive::xml_oarchive archive(stream);
archive << BOOST_SERIALIZATION_NVP(t);
std::string serialized = stream.str();
return serialized;
}
template<typename T>
void deserialize(const std::string& serialized, T& t) {
std::stringstream stream(serialized);
boost::archive::xml_iarchive archive(stream);
archive >> BOOST_SERIALIZATION_NVP(t);
}
};
}
}
#endif
sample usage
#include <iostream>
#include <demo/common/serializer.hpp>
#include <demo/common/command.hpp>
using namespace std;
using namespace demo::common;
int main(){
serializer serializer_;
command r("123"); // <-- (1) my desired way of declaring
//request* r = new command("123"); <-- (2) replacing with this makes all work!
//command* r = new command("123"); <-- (3) replacing with this crashes the app, like (1)
std::string s = serializer_.serialize(r);
std::cout << s << std::endl;
request* rr = nullptr;
serializer_.deserialize(s, rr); //this throws an exception
command* rrr = dynamic_cast<command*>(rr);
rrr->execute();
}
I thought I did everything that needs to be done, archives included before any class export, all default constructors initialize members..
Note that the serializable classes and the serializer are compiled to a lib file. Then that lib is used in two sub-projects that have access to the headers and have that lib linked. They use those classes to communicate with each other, they send serialized objects over network.
Why can't I deserialize a derived class to a base class pointer?
I am using Boost 1.51 and VC11.
Problems:
The two major things I found finicky and not documented enough about Boost::serialization that caused me issues are as follows:
Serialization / deserialization of objects on the stack mixed with objects on the heap. For example if you serialize from a object on the stack then attempt to deserialize to a pointer (e.g. invoke your load_construct_data<>) an exception may occur. Same with the reverse scenario.
Not having your exports linked in properly. If you create serialization templates/classes and place them in a .lib for example, it seems the exports may not be properly linked in / exposed. This goes for linking in and then using from a shared object/DLL.
Solutions:
For #1, I've found it easiest to make a rule of always serializing/deserializing to/from pointers. Even objects on the stack can use a temporary pointer when serializing to allow for this rule. For example:
// serialize
MyObject myobj;
std::ostringstream oss;
boost::archive::text_oarchive oa(oss);
MyObject* myObjPtr = &myObj;
oa << myObjPtr; // this is different than oa << myObj!!
std::string serialized = oss.str();
// deserialize
MyObject* myNewObjPtr;
std::stringstream iss(serialized);
boost::archive::text_iarchive ia(iss);
ia >> myNewObjPtr; // invokes new, don't forget to delete (or use smart ptrs!!!)
For #2, simply create a .cpp file that contains all of your exports. Link this CPP into your module(s) directly. In other words, you'll have a .cpp with a bunch of BOOST_CLASS_EXPORT_IMPLEMENT():
BOOST_CLASS_EXPORT_IMPLEMENT(MyObject);
// ...
More Complete Example:
Below is a more complete example showing some of the serialization tricks using non-intrusive templates. Intrusive member methods will be very similar:
MyObject.h
// Can be broken into MyObject.h, MyObject.cpp, MyObjectSerialization.h for example as well.
// This stuff can live in your .lib
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
// assume this class contains GetSomeMember() returning SomeMemberType
class MyObject { /* ... */ };
BOOST_CLASS_EXPORT_KEY(MyObject);
namespace boost { namespace serialization {
template<class Archive>
void serialize(Archive& ar, MyObject& myObj, const unsigned int version)
{
ar & myObj.m_someMember;
}
template<class Archive>
inline void save_construct_data(Archive& ar, const MyObject* myObj, const unsigned int version)
{
ar & boost::serialization::make_nvp("SomeMemberType", static_cast<const SomeMemberType&>(myObj->GetSomeMember()));
}
template<class Archive>
inline void load_construct_data(Archive& ar, MyObject* myObj, const unsigned int version)
{
SomeMemberType t;
ar & boost::serialization::make_nvp("SomeMemberType", t);
::new(myObj)MyObject(t);
}
} } // end boost::serialization ns
MyObjectExports.cpp
// This file must be explicitly linked into your module(s) that use serialization.
// This means your executable or shared module/DLLs
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include "MyObject.h"
BOOST_CLASS_EXPORT_IMPLEMENT(MyObject);
You're probably getting an input_stream_error in your demo and unregistered_class exception when using your library. This is caused by the way boost is registering the classes, in your case, automatically.
It appears that the automatic registration process gets confused when you serialize a derived object and deserialize to its base, despite the use of the BOOST_CLASS_EXPORT* macros.
However, you can register the classes explicitly before you perform any i/o operation on the archive:
// ...
boost::archive::xml_iarchive archive(stream);
// register the class(es) with the archive
archive.template register_type<command>();
archive >> BOOST_SERIALIZATION_NVP(t);
// ...
Use the same order of registration when serializing. This makes the export macros superfluous.