Continuing from the question that I asked here: C++ multi-dimensional data handling
In my example: I have many Chips, each Chip has many Registers, each Register has many Cells, and each Cell has many Transistors. I asked whether to use one complex STL container for them, or to implement full classes for them. And, as advised, I chose to implement full classes for them. I have:
class Chip
{
map<RegisterLocation, Register> RegistersPerLocation;
};
class Register
{
map<CellLocation, Cell> CellsPerLocation;
};
// etc..
Now, I need to fill the data to the classes, and I can't decide: Should reading the data be responsibility of these classes, or should they just wrap the containers and the reading will be done outside.
I mean I have to choose one of the following: Either:
class Chip
{
map<RegisterLocation, Register> RegistersPerLocation;
public:
void AddRegisterPerLocation(RegisterLocation, Register);
};
void ReadChipData(Chip & chip)
{
for (RegisterLocation loc = 0; loc < 10; loc++)
{
Register reg;
ReadReg(reg);
chip.AddRegisterPerLocation(loc, reg);
}
}
void ReadReg(Register & reg)
{
for (CellLocation loc = 0; loc < 10; loc++)
{
Cell cell;
ReadCell(cell);
reg.AddRegisterPerLocation(loc, cell);
}
}
//etc...
Or:
class Chip
{
map<RegisterLocation, Register> RegistersPerLocation;
public:
void ReadData();
};
void Chip::ReadData()
{
for (RegisterLocation loc = 0; loc < 10; loc++)
{
Register reg;
reg.ReadData();
RegistersPerLocation[loc] = reg;
}
}
//etc...
void ReadChipData(Chip & chip)
{
chip.ReadData();
}
Thank you.
If you are thinking of tying the reader/writer to the domain objects in order to follow the principle of encapsulation, you are correct to a certain extent. But remember: You bind not just any action, but a valid behavior. Valid as in makes sense for the object in the domain.
Another thing to keep in mind is separation of concerns. Serializability is not a Chip's intrinsic behavior -- modeling that into the domain object would be unfair IMO. YMMV.
Separate the reading(and writing) from the classes. As the library does. Expose iterators if you have to. And you can overload the '<<' and '>>' operators for syntactic sugar ;)
A minor nit on the classes -- a template based approach looks so promising.
Here's some code I cooked up: you can try the following as well.
(I've successfully compiled and run this on a MS VS2005 but check it out on your system. Also, can someone fix the tabbing -- feeling too lazy to do this :P)
/*+-----------8<----------------------------8<-----------+*/
#include <vector>
#include <iostream>
#include <algorithm>
#include <map>
#include <iterator>
/* mother template */
template<class _Item>
struct Hardware
{
Hardware() : _myCont(2 + ::rand() % 5) {}
private:
typename vector<_Item> _myCont;
// i/o friends
template<class _Item>
friend ostream& operator<<(ostream& output,
const Hardware<_Item>& me);
template<class _Item>
friend istream& operator>>(istream& in,
const Hardware<_Item>& me);
};
/* actual domain objects */
/* base object */
struct Transistor {
};
/* built objects */
typedef Hardware<Transistor> Cell;
typedef Hardware<Cell> Register;
typedef Hardware<Register> Chip;
/* poorman's introspection utility */
template<class T>
const char *who() { return ""; }
template<>
const char *who<Transistor>() { return "Transistor"; }
template<>
const char *who<Cell>() { return "Cell"; }
template<>
const char *who<Register>() { return "Register"; }
template<>
const char *who<Chip>() { return "Chip"; }
/* writer/serialize out */
template<class T>
ostream& operator<<(ostream& out, const Hardware<T>& hw) {
// whatever you need to do to write
// os << chip works fine, because you will provide a specialization
out << "[ " << ::who<Hardware<T>>() << " ]\n\t";
std::copy(hw._myCont.begin(), hw._myCont.end(),
std::ostream_iterator< T >(std::cout, "\n\t"));
return out;
}
/* specialize for base object */
ostream& operator<< (ostream& out, const Transistor& hw) {
out << "[ " << ::who<Transistor>() << " ]\n";
return out;
}
/* reader/serialize in */
template<class T>
istream& operator>>(istream& in, const Hardware<T>& hw) {
// whatever you need to do to read
// similarly in >> chip works fine,
return in;
}
// driver showing relationships
// Chip -> Register -> Cell -> Transistor
int main() {
Transistor t;
std::cout << t << std::endl;
Cell ce;
std::cout << ce << std::endl;
Register r;
std::cout << r << std::endl;
Chip C;
std::cout << C << std::endl;
}
/*+-----------8<----------------------------8<-----------+*/
Caveat: Haven't tested, so there may be quite a few compiler errors/warnings. But this should give you an idea of what I am trying to say.
Making classes responsible for their serialization is better - once you change the class fields you have to change the same class serializeation method, not some reader/writer code over there.
Related
I would like to define something like a new cout, which I can use to log my data:
some_type cout2( Message_type message ){
cout << message;
logfile.save(message);
}
so I will use it with
cout2 << "some message" << endl;
Up to now I was not able to find out how the code above has to look like exactly.
Thanks for your help.
You can create your own logger like:
class Logger {
public:
Logger(std::string const& filename)
: stream_(filename, std::ofstream::out | std::ofstream::app)
{
if (!stream_) {
// Error...
}
}
Logger& operator<<(std::string const& str) {
std::cout << str;
stream_ << str;
return *this;
}
private:
std::ofstream stream_;
};
Generally speaking, classes from C++ standard library are not designed to be derived, and that is true for stream classes.
So IMHO, it is better to specify what method you will need on your cout2 object, and then:
design a class containing a ostream& object, initialized in ctor
delegate actual output to that internal object
do whatever log you need in your methods
You should use templated a operator << to be able to easily process any class that std::ostream can process.
class LogStream {
std::ostream& out;
Logfile logfile;
LogStream(std::ostream& out, /* param for logfile initialization */ ...)
: out(out), logfile(...) {}
... // other useful methods for state
};
template<typename T>
LogStream& operator << (LogStream& out, T val) {
out.out << message;
// should first test whether T is manipulator, and choose whether and how it should be logged
logfile.save(message);
}
You don't want to modify std::cout.
Instead, you want to create a specialised std::streambuf that writes to two buffers rather than one. For example;
#include <streambuf>
template <typename char_type,
typename traits = std::char_traits<char_type> >
class basic_teebuf:
public std::basic_streambuf<char_type, traits>
{
public:
typedef typename traits::int_type int_type;
basic_teebuf(std::basic_streambuf<char_type, traits> * sb1,
std::basic_streambuf<char_type, traits> * sb2)
: sb1(sb1)
, sb2(sb2)
{
}
protected: // override virtuals inherited from std::basic_streambuf
virtual int sync()
{
int const r1 = sb1->pubsync();
int const r2 = sb2->pubsync();
return r1 == 0 && r2 == 0 ? 0 : -1;
}
virtual int_type overflow(int_type c)
{
int_type const eof = traits::eof();
if (traits::eq_int_type(c, eof))
{
return traits::not_eof(c);
}
else
{
char_type const ch = traits::to_char_type(c);
int_type const r1 = sb1->sputc(ch);
int_type const r2 = sb2->sputc(ch);
return
traits::eq_int_type(r1, eof) ||
traits::eq_int_type(r2, eof) ? eof : c;
}
}
private:
std::basic_streambuf<char_type, traits> * sb1;
std::basic_streambuf<char_type, traits> * sb2;
};
typedef basic_teebuf<char> teebuf;
Then you need to create a specialised ostream which uses such a buffer
#include <ostream>
class teestream : public std::ostream
{
public:
// Construct an ostream which tees output to the supplied
// ostreams.
teestream(std::ostream & o1, std::ostream & o2);
private:
teebuf tbuf;
};
teestream::teestream(std::ostream & o1, std::ostream & o2)
: std::ostream(&tbuf)
, tbuf(o1.rdbuf(), o2.rdbuf())
{
}
All the above does is create a specialised std::ostream that uses our specialised buffer, which in turn makes use of two buffers.
Now, our teestream needs to be initialised using two streams. For example
#include <fstream>
#include <iostream>
// include the preceding definition of teestream here
int main()
{
std::ofstream logfile("hello-world.log");
teestream tee(std::cout, logfile);
// tee is now a stream that writes the same output to std::cout and logfile
tee << "Hello, world!\n";
return 0;
}
The advantage of this is that all stream insertions (operator <<) will work with our teestream - even for classes with overloaded versions.
When main() returns, the streams will also be closed cleanly.
I've written the specalised streambuf as a template (std::streambuf is a specialisation of a templated class named std::basic_streambuf). For generality, it would probably be better to do the same with the stream (using the fact that std::ostream is also a specialisation of a templated std::basic_ostream). I leave that sort of generalisation as an exercise.
The logging systems I've seen, built on this idea look something like this:
#define MY_PRINT(x, ...) \
{ \
fprintf(logFile, x, ##__VA_ARGS__); \
fflush(acuLogFile); \
}
And you would use it like:
MY_PRINT("this is just a test\n");
Even though this is the C way of doing things, it is very versatile and work in C++ as well.
You just have to use your newly define print function everywhere in the code.
Maybe you should have an instance based logger with a .log() method, that, depending on implementation can log to file, write to stdout etc, rather than trying to overload free functions from std namespace?
Your intent will be clearer and it will be more object orientated.
In fact here is an example of a pretty cookie-cutter event logger I wrote recently, if that helps you to understand the sort of thing I'm talking about. My design allows for dependnacy injection, as well as keeping boring decisions about how something should be formatted as output and where it should go (stdout or file etc) in the logger.
Of course you can define your own cout.
First you need a class which can handle the << operator and contains an fstream and an ostream (like cout).
class logstream
{
private:
fstream *filestream;
ostream *cout;
public:
logstream(fstream* filestream, ostream* cout)
{
this->cout = cout;
this->filestream = filestream;
}
string operator<< (char* s)
{
*cout << s;
*filestream << s;
return s;
}
};
To use this, simply initialize it with cout and a fstream.
fstream lout = fstream("Filename", ios::app);
logstream cout = logstream(&lout, &std::cout);
And now you have what you wanted.
cout << "message";
EDIT: Don't forget to include
#include <iostream>
#include <fstream>
and
using namespace std;
I have two classes that will represent two very simple databases, and each has a "Save" function which will write what's in the class to a file. Since the code within the "Save" function is very similar, I was wondering if I could factor it out.
One of my colleagues said this might be possible with inheritance and/or metadata, so I tried looking into it myself with Google. However, I couldn't find anything that was helpful and am still unsure if what I want to do is even possible.
If it's possible to factor out, then I think I'd need to have another class or function know about each class's types and iterate through them somehow (metadata?). It would check the type of every data, and depending on what the type is, it would make sure that it's correctly output to the text file.
(I know data like name, age, etc. should be private, but to keep this simple I just had everything be public)
class A
{
public:
A() : name(""), age(0) {};
void Save(void)
{
std::string filename = "A.txt";
std::string data;
data += name + "\n";
data += std::to_string(age) + "\n";
std::ofstream outfile(filename);
outfile.write(data.c_str(), data.size());
outfile.close();
}
std::string name;
int age;
};
class B
{
public:
B() : ID(0), points(0) {};
void Save(void)
{
std::string filename = "B.txt";
std::string data;
data += std::to_string(ID) + "\n";
data += std::to_string(points) + "\n";
std::ofstream outfile(filename);
outfile.write(data.c_str(), data.size());
outfile.close();
}
int ID;
int points;
};
int main(void)
{
A a;
B b;
a.name = "Bob"; a.age = 20;
b.ID = 4; b.points = 95;
a.Save();
b.Save();
return 0;
}
A possible solution could be to use metaprogramming (not sure what you mean by metadata), i.e. templates to reuse the common parts
template<typename T1, typename T2>
void TSave(const std::string fname, const T1& p1, const T2& p2) {
std::string filename = fname;
std::stringstream data;
data << p1 << "\n";
data << p2 << "\n";
std::ofstream outfile(filename);
outfile.write(data.str().c_str(), data.str().size());
outfile.close();
}
class A {
...
void Save(void) {
TSave("A.txt", name, age);
}
std::string name;
int age;
};
class B {
...
void Save(void) {
TSave("B.txt", ID, points);
}
int ID;
int points;
};
Live Example
What you are looking for is serialization: saving objects to a file (and one day or another, restore the objects).
Of course, you could write your own serialization framework, and Marco's answer is an interesting start in that direction. But alternatively, you could consider existing libraries, such as boost::serialization :
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
class A {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & name;
ar & age;
}
...
};
class B {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & ID;
ar & points;
}
...
};
main() {
A a;
B b;
...
{
std::ofstream ofs("myfile");
boost::archive::text_oarchive arch(ofs);
arch << a << b;
}
}
As you see, it's still needed to say what's to be written to the file. However, the code is simplified : you don't have to worry about file management and transformation of data. And it works also with standard containers.
You won't find a C++ trick that automatically determines for a class what's to be saved. Two reasons for that:
C++ allows metaprogramming, but it is not reflexive: there are no standard process to find out at execution time which members compose a class.
In an object, some data can be transient, i.e. it means only something at the time of the execution and depends on the context. For example pointers: you could save the value of a pointer to a file, but it will mean nothing when you reload it later (the pointer is only valid until you free the object). The proper way would be to save the object that is pointed to (but where, when, how?).
I'm trying to write a stream manipulator with arguments.
I have class with 3 int's CDate(Year, Month, Day).
So I need to make manipulator date_format(const char*).
e.g. :
CDate a(2006, 5, 15);
cout <<"DATE IS : " << date_format("%Y-hello-%d-world-%m-something-%d%d") << a;
Output will be :
DATE IS : 2006-hello-15-world-5-something-1515
Guess i need to use that
ios_base & dummy_date_format_manipulator ( ios_base & x )
{
return x;
}
ios_base & ( * ( date_format ( const char * fmt ) ) )( ios_base & x )
{
return dummy_date_format_manipulator;
}
but i don't know how.
You can use pword array for this.
Every iostream in C++ has two arrays associated with it.
ios_base::iword - array of ints
ios_base::pword - array of void* pointers
You can store you own data in it. To obtain an index, that refers to an empty element in all iword and pword arrays you should use function std::ios_base::xalloc(). It returns int that you can use as an unique index in *word.
You should obtain that index once on the start-up, and than use it for all operations with *word.
Then programming your own manip will look like:
Manipulator function, that receives reference to ios_base object and pointer to the format string, simply stores that pointer in pword
iosObject.pword(index_from_xalloc) = formatString
Then overloaded operator << (>>) obtains format string from the iostream object in the same way. After that you just make a conversion referencing to the format.
Manipulators with arguments don't work the same as those without arguments! The are just classes with a suitable output operator which instead of outputting a value manipulate the stream's state. To manipulate the stream state you'll probably set up a suitabe value stored with an iword() or a pword() associated with the dtream and used by the output operator.
As chris suggested, I'd say that you should just use tm rather than your custom date class:
tm a{0, 0, 0, 15, 5, 2006 - 1900};
cout << put_time(&a, "%Y-hello-%d-world-%m-something-%d%d");
If you must implement come custom functionality that cannot be accomplished with get_time and put_time then you'd probably want to use a tm member as part of your class so you could just extend the functionality that is already there:
class CDate{
tm m_date;
public:
CDate(int year, int month, int day): m_date{0, 0, 0, day, month, year - 1900}{}
const tm& getDate() const{return m_date;}
};
ostream& operator<<(ostream& lhs, const CDate& rhs){
auto date = rhs.getDate();
return lhs << put_time(&a, "%Y-hello-%d-world-%m-something-%d%d");
}
You could then use CDate as follows:
CDate a(2006, 5, 15);
cout << "DATE IS:" << a;
EDIT:
After looking at your question again, I think that you have a misconception about how the insertion operator works, you cannot pass in both an object and a format: https://msdn.microsoft.com/en-us/library/1z2f6c2k.aspx
If you want to specify a format but still retain your CDate class, I'd again suggest the use of put_time:
cout << put_time(&a.getDate(), "%Y-hello-%d-world-%m-something-%d%d");
If you again insist on writing your own format accepting function you'll need to create a helper class that can be constructed inline and support that with the insertion operator:
class put_CDate{
const CDate* m_pCDate;
const char* m_szFormat;
public:
put_CDate(const CDate* pCDate, const char* szFormat) : m_pCDate(pCDate), m_szFormat(szFormat) {}
const CDate* getPCDate() const { return m_pCDate; }
const char* getSZFormat() const { return m_szFormat; }
};
ostream& operator<<(ostream& lhs, const put_CDate& rhs){
return lhs << put_time(&rhs.getPCDate()->getDate(), rhs.getSZFormat());
}
You could use this as follows:
cout << put_CDate(&a, "%Y-hello-%d-world-%m-something-%d%d") << endl;
Like Dietmar said you can push the params into the iword() but i find this kind of solution to be tedious and annoying..
I prefer to just install lambda functions as a iomanips and use them to directly call into the various classes methods or otherwise build the custom print in place. For that purpose I have created a simple 100 line template installer/helper class for mainpulators which can add a lambda function as the manipulator of any class..
So for your CDate you might define you manip as
std::ostream& dummy_date_format_manipulator (std::ostream& os)
{
CustomManip<CDate>::install(os,
[](std::ostream& oos, const CDate& a)
{
os << a.year()
<< "-hello-"
<< a.day()
<< "-world-"
<< a.month()
<< "-something-"
<< a.day() << a.day();
});
return os;
}
Then just direct the << op to use the manip installers print helper:
std::ostream& operator<<(std::ostream& os, const CDate& a)
{
CustomManip<CDate>::print(os, a);
return os;
}
And your basically done..
The mainp installer code and a fully working example is in my blog post at:
http://code-slim-jim.blogspot.jp/2015/04/creating-iomanip-for-class-easy-way.html
But to be nice.. here is the key part you want to put in a .h somewhere less all the printouts to demonstrate how it works:
//g++ -g --std=c++11 custom_class_manip.cpp
#include <iostream>
#include <ios>
#include <sstream>
#include <functional>
template <typename TYPE>
class CustomManip
{
private:
typedef std::function<void(std::ostream&, const TYPE&)> ManipFunc;
struct CustomManipHandle
{
ManipFunc func_;
};
static int handleIndex()
{
// the id for this Custommaniputors params
// in the os_base parameter maps
static int index = std::ios_base::xalloc();
return index;
}
public:
static void install(std::ostream& os, ManipFunc func)
{
CustomManipHandle* handle =
static_cast<CustomManipHandle*>(os.pword(handleIndex()));
// check if its installed on this ostream
if (handle == NULL)
{
// install it
handle = new CustomManipHandle();
os.pword(handleIndex()) = handle;
// install the callback so we can destroy it
os.register_callback (CustomManip<TYPE>::streamEvent,0);
}
handle->func_ = func;
}
static void uninstall(std::ios_base& os)
{
CustomManipHandle* handle =
static_cast<CustomManipHandle*>(os.pword(handleIndex()));
//delete the installed Custommanipulator handle
if (handle != NULL)
{
os.pword(handleIndex()) = NULL;
delete handle;
}
}
static void streamEvent (std::ios::event ev,
std::ios_base& os,
int id)
{
switch (ev)
{
case os.erase_event:
uninstall(os);
break;
case os.copyfmt_event:
case os.imbue_event:
break;
}
}
static void print(std::ostream& os, const TYPE& data)
{
CustomManipHandle* handle
= static_cast<CustomManipHandle*>(os.pword(handleIndex()));
if (handle != NULL)
{
handle->func_(os, data);
return;
}
data.printDefault(os);
}
};
Of course if you really do need the parameters then use the CustomManip::make_installer(...) function to get it done but for that you will have to visit the blog..
I have a simple GUI program that uses a custom stringstream to redirect output from the console to a text field in the GUI (under some circumstances). currently. the window redraws any time I hit enter, but it's possible that output could be generated at other times. Is there a way to register a function with the stringstream that gets executed every time the << operator is used on the stream?
NOTE
I should have pointed out that I cannot use C++11 in my solution. the machines on which this will be compiled and run will not have c++11 available.
Personally, I wouldn't use an std::ostringstream (or even an std::stringstream) for this at all! Instead, I would create my own stream buffer taking care of sending the data to the GUI. That is, I'd overwrite std::streambuf::overflow() and std::streambuf::sync() to send the current data to the GUI. To also make sure that any output is sent immediately, I'd set up an std::ostream to have std::ios_base::unitbuf set. Actually, sending the changes to a function is quite simple, i.e., I'll implement this:
#include <streambuf>
#include <ostream>
#include <functional>
#include <string>
#include <memory>
#include <iostream> // only for testing...
#if HAS_FUNCTION
typedef std::function<void(std::string)> function_type;
#else
class function_type
{
private:
struct base {
virtual ~base() {}
virtual base* clone() const = 0;
virtual void call(std::string const&) = 0;
};
template <typename Function>
struct concrete
: base {
Function d_function;
concrete(Function function)
: d_function(function) {
}
base* clone() const { return new concrete<Function>(this->d_function); }
void call(std::string const& value) { this->d_function(value); }
};
std::auto_ptr<base> d_function;
public:
template <typename Function>
function_type(Function function)
: d_function(new concrete<Function>(function)) {
}
function_type(function_type const& other)
: d_function(other.d_function->clone()) {
}
function_type& operator= (function_type other) {
this->swap(other);
return *this;
}
~function_type() {}
void swap(function_type& other) {
std::swap(this->d_function, other.d_function);
}
void operator()(std::string const& value) {
this->d_function->call(value);
}
};
#endif
class functionbuf
: public std::streambuf {
private:
typedef std::streambuf::traits_type traits_type;
function_type d_function;
char d_buffer[1024];
int overflow(int c) {
if (!traits_type::eq_int_type(c, traits_type::eof())) {
*this->pptr() = traits_type::to_char_type(c);
this->pbump(1);
}
return this->sync()? traits_type::not_eof(c): traits_type::eof();
}
int sync() {
if (this->pbase() != this->pptr()) {
this->d_function(std::string(this->pbase(), this->pptr()));
this->setp(this->pbase(), this->epptr());
}
return 0;
}
public:
functionbuf(function_type const& function)
: d_function(function) {
this->setp(this->d_buffer, this->d_buffer + sizeof(this->d_buffer) - 1);
}
};
class ofunctionstream
: private virtual functionbuf
, public std::ostream {
public:
ofunctionstream(function_type const& function)
: functionbuf(function)
, std::ostream(static_cast<std::streambuf*>(this)) {
this->flags(std::ios_base::unitbuf);
}
};
void some_function(std::string const& value) {
std::cout << "some_function(" << value << ")\n";
}
int main() {
ofunctionstream out(&some_function);
out << "hello" << ',' << " world: " << 42 << "\n";
out << std::nounitbuf << "not" << " as " << "many" << " calls\n" << std::flush;
}
A fair chunk of the above code is actually unrelated to the task at hand: it implements a primitive version of std::function<void(std::string)> in case C++2011 can't be used.
If you don't want quite as many calls, you can turn off std::ios_base::unitbuf and only sent the data upon flushing the stream, e.g. using std::flush (yes, I know about std::endl but it unfortunately is typically misused to I strongly recommend to get rid of it and use std::flush where a flush is really meant).
In order to do this you should create your own streambuf class. streambuf classes represent IO devices and each one takes care of the various issues specific to that kind of device. The standard defines a streambuf for files and another for strings. Network access would use another, and output to a GUI should also be represented as another kind of device if you're going to use streams at all.
Writing an appropriate streambuf class isn't trivial and seems to be kind obscure, but there are resources out there. The C++ Standard Library - A Tutorial and Reference has a small section on this. Standard C++ IOStreams and Locales: Advanced Programmer's Guide and Reference provides in-depth information. A search for subclassing basic_streambuf will also turn up some free resources online.
If you haven't already, can you derive a subclass from stringstream and overload its stream insertion operator to generate events?
Pseudocode:
class AlertingStream : public stringstream
{
ostream& operator << (type)
{
for (each listener in listeners)
{
listener.notify();
}
perform insertion;
return *this;
}
}
I have encounter a problem in my project on enums.
In EventDef.h,
enum EventDef {
EVT1 = 0,
EVT2,
EVT3,
EVT_NUM,
}
In this way, I can extend the EventDef system in another header UIEventDef.h by
#include "EventDef.h"
enum UIEventDef {
UIEVT1 = EVT_NUM,
UIEVT2,
UIEVT3,
}
But, there is a limitation that i can not do this in NetEvent.h the same way.
#include "EventDef.h"
enum NetEventDef {
NETEVT1 = EVT_NUM,
NETEVT2, //wrong: this will have the same value as UIEVT2
NETEVT3,
}
Is there a better compile time solution in C++ such as templates that can help ?
The idea of extensible enums is not inherently "bad design". In other languages there is a history of them, even if c++ does not support them directly. There are different kinds of extensibility.
Things that extensible enums would be useful for
error codes
message types
device identification (OIDs are a hierarchical enum like system)
Examples of enum extensibility
Objective Modula Two has enums that are extensible with a class like inheritance.
The Extensible Enum Pattern in Java, which can be implemented in c++.
Java enums are extensible in that extra data and methods can be a part of an enum.
In c++, the typeid operator is essentially a compiler generated enum with attached values.
The kind of extensibility you showed in your sample code does not have an elegant implementation in unaided c++. In fact, as you pointed out, it easily leads to problems.
Think about how you are wanting to use an extensible enum. Perhaps a set/map of immutable singleton objects will meet your needs.
Another way to have extensible enums in c++ is to use a code generator. Every compilation unit that wants to add to an extensible enum, records the ids in its own, separate, .enum file. At build time, before compilation, a script (ie perl, bash, ...) looks for all .enum files, reads them, assigns numeric values to each id, and writes out a header file, which is included like any other.
Why do you want your event enums to be declared like that? What do you gain by having them 'linked' if you will, they way you describe?
I would make them completely independent enums. Secondly, I recommend you not use the old style enums anymore. c++11 is here and available in gcc. You should use enum classes:
enum class EventDef : unsigned { Evt1 = 0, Evt2, Evt3, ... LastEvt }
enum class NetEvtDef : unsigned { NetEvt1 = 0, NetEvt2, NetEvt3, ... NetLastEvt }
If you are switching you can do this:
void doSwitch(EventDef evt_def)
{
switch(evt_def)
{
case EventDef::Evt1
{
// Do something;
break;
}
default:
// Do something;
};
}
void doSwitch(NetEvtDef net_def)
{
switch(net_def)
{
case NetEvtDef::NetEvt1
{
// Do something;
break;
}
default:
// Do something;
};
}
By creating an overloaded function for doSwitch you segregate all your enum types. Having them in separate categories is a benefit not a problem. It provides you the flexibility to deal with each event enum type differently.
Chaining them together as you describe needlessly complicates the problem.
I hope that helps.
I find the following a useful compromise between complexity, features, and type safety. It uses global variables of a custom class that has a default constructor to make initialisation easy. The example below is an extendable set of error codes. You might want to enclose within a name space also (but I typically don't bother).
//
// ErrorCodes.h
// ExtendableEnum
//
// Created by Howard Lovatt on 10/01/2014.
//
#ifndef ErrorCodes_h
#define ErrorCodes_h
#include <string>
class ErrorCodes {
public:
static int nextValue_;
explicit ErrorCodes(std::string const name) : value_{nextValue_++}, name_{name} {}
ErrorCodes() : ErrorCodes(std::to_string(nextValue_)) {}
int value() const { return value_; }
std::string name() const { return name_; }
private:
int const value_;
std::string const name_;
ErrorCodes(const ErrorCodes &);
void operator=(const ErrorCodes &);
};
int ErrorCodes::nextValue_ = 0; // Weird syntax, does not declare a variable but rather initialises an existing one!
ErrorCodes first;
ErrorCodes second;
// ...
#endif
//
// ExtraErrorCodes.h
// ExtendableEnum
//
// Created by Howard Lovatt on 10/01/2014.
//
#ifndef ExtraErrorCodes_h
#define ExtraErrorCodes_h
#include "ErrorCodes.h"
ErrorCodes extra{"Extra"};
#endif
//
// ExtraExtraExtraCodes.h
// ExtendableEnum
//
// Created by Howard Lovatt on 10/01/2014.
//
#ifndef ExtendableEnum_ExtraExtraCodes_h
#define ExtendableEnum_ExtraExtraCodes_h
#include "ErrorCodes.h"
ErrorCodes extraExtra{"ExtraExtra"};
#endif
//
// main.cpp
// ExtendableEnum
//
// Created by Howard Lovatt on 10/01/2014.
//
#include <iostream>
#include "ErrorCodes.h"
#include "ExtraErrorCodes.h"
#include "ExtraExtraErrorCodes.h"
// Need even more error codes
ErrorCodes const localExtra;
int main(int const notUsed, const char *const notUsed2[]) {
std::cout << first.name() << " = " << first.value() << std::endl;
std::cout << second.name() << " = " << second.value() << std::endl;
std::cout << extra.name() << " = " << extra.value() << std::endl;
std::cout << extraExtra.name() << " = " << extraExtra.value() << std::endl;
std::cout << localExtra.name() << " = " << localExtra.value() << std::endl;
return 0;
}
The output is:
0 = 0
1 = 1
Extra = 2
ExtraExtra = 3
4 = 4
If you have multiple compilation units then you need to use a variation on the singleton pattern:
class ECs {
public:
static ErrorCode & first() {
static ErrorCode instance;
return instance;
}
static ErrorCode & second() {
static ErrorCode instance;
return instance;
}
private:
ECs(ECs const&);
void operator=(ECs const&);
};
We can construct an extensible “enum” in C++ as follows:
struct Last {};
struct D
{
using Next = Last;
static const char* name = “D”;
};
struct C
{
using Next = D;
static const char* name = “C”;
};
struct B
{
using Next = C;
static const char* name = “B”;
};
using First = B;
We can iterate thru the above using these constructs:
void Process(const B&)
{
// do something specific for B
cout << “Call me Ishmael” << endl;
}
template <class T>
void Process(const T&)
{
// do something generic
cout << “Call me “ << T::name << endl;
}
template <class T>
struct IterateThru
{
static void iterate()
{
Process(T());
IterateThru<T::Next>::iterate();
}
};
template <>
struct IterateThru<Last>
{
static void iterate()
{
// end iteration
}
};
To iterate through the “enumeration”:
IterateThru<First>::iterate();
To extend the “enumeration”:
struct A
{
using Next = B;
static const char* name = “A”;
}:
using First = A: