How to handle different protocol versions transparently in c++? - c++

This is a generic C++ design question.
I'm writing an application that uses a client/server model. Right now I'm writing the server-side. Many clients already exist (some written by myself, others by third parties). The problem is that these existing clients all use different protocol versions (there have been 2-3 protocol changes over the years).
Since I'm re-writing the server, I thought it would be a great time to design my code such that I can handle many different protocol versions transparently. In all protocol versions, the very first communication from the client contains the protocol version, so for every client connection, the server knows exactly which protocol it needs to talk.
The naive method to do this is to litter the code with statements like this:
if (clientProtocolVersion == 1)
// do something here
else if (clientProtocolVersion == 2)
// do something else here
else if (clientProtocolVersion == 3)
// do a third thing here...
This solution is pretty poor, for the following reasons:
When I add a new protocol version, I have to find everywhere in the source tree that these if statements are used, and modify them to add the new functionality.
If a new protocol version comes along, and some parts of the protocol version are the same as another version, I need to modify the if statements so they read if (clientProtoVersion == 5 || clientProtoVersion == 6).
I'm sure there are more reasons why it's bad design, but I can't think of them right now.
What I'm looking for is a way to handle different protocols intelligently, using the features of the C++ langauge. I've thought about using template classes, possibly with the template parameter specifying the protocol version, or maybe a class heirarchy, one class for each different protocol version...
I'm sure this is a very common design pattern, so many many people must have had this problem before.
Edit:
Many of you have suggested an inheritance heirarchy, with the oldest protocol version at the top, like this (please excuse my ASCII art):
IProtocol
^
|
CProtoVersion1
^
|
CProtoVersion2
^
|
CProtoVersion3
... This seems like a sensible thing to do, in terms of resuse. However, what happens when you need to extend the protocol and add fundamentally new message types? If I add virtual methods in IProtocol, and implement these new methods in CProtocolVersion4, how are these new methods treated in earlier protocol versions? I guess my options are:
Make the default implementation a NO_OP (or possibly log a message somewhere).
Throw an exception, although this seems like a bad idea, even as I'm typing it.
... do something else?
Edit2:
Further to the above issues, what happens when a newer protocol message requires more input than an older version? For example:
in protocl version 1, I might have:
ByteArray getFooMessage(string param1, int param2)
And in protocol version 2 I might want:
ByteArray getFooMessage(string param1, int param2, float param3)
The two different protocol versions now have different method signatures, which is fine, except that it forces me to go through all calling code and change all calls with 2 params to 3 params, depending on the protocol version being used, which is what I'm trying to avoid in the first place!
What is the best way of separating protocol version information from the rest of your code, such that the specifics of the current protocol are hidden from you?

Since you need to dynamically choose which protocol to use, using different classes (rather than a template parameter) for selecting the protocol version seems like the right way to go. Essentially this is Strategy Pattern, though Visitor would also be a possibility if you wanted to get really elaborate.
Since these are all different versions of the same protocol, you could probably have common stuff in the base class, and then the differences in the sub classes. Another approach might be to have the base class be for the oldest version of the protocol and then have each subsequent version have a class that inherits from the previous version. This is a somewhat unusual inheritance tree, but the nice thing about it is that it guarantees that changes made for later versions don't affect older versions. (I'm assuming the classes for older versions of the protocol will stabilize pretty quickly and then rarely ever change.
However you decide to organize the hierarchy, you'd then want to chose the protocol version object as soon as you know the protocol version, and then pass that around to your various things that need to "talk" the protocol.

I have used (and heard of others using) templates to solve this problem too. The idea is that you break your different protocols up into basic atomic operations and then use something like boost::fusion::vector to build protocols out of the individual blocks.
The following is an extremely rough (lots of pieces missing) example:
// These are the kind of atomic operations that we can do:
struct read_string { /* ... */ };
struct write_string { /* ... */ };
struct read_int { /* ... */ };
struct write_int { /* ... */ };
// These are the different protocol versions
typedef vector<read_string, write_int> Protocol1;
typedef vector<read_int, read_string, write_int> Protocol2;
typedef vector<read_int, write_int, read_string, write_int> Protocol3;
// This type *does* the work for a given atomic operation
struct DoAtomicOp {
void operator ()(read_string & rs) const { ... }
void operator ()(write_string & ws) const { ... }
void operator ()(read_int & ri) const { ... }
void operator ()(write_int & wi) const { ... }
};
template <typename Protocol> void doProtWork ( ... ) {
Protocl prot;
for_each (prot, DoAtomicOp (...));
}
Because the protocl version is dynamic you'll need a single top level switch statement to determine which protocl to use.
void doWork (int protocol ...) {
switch (protocol) {
case PROTOCOL_1:
doProtWork<Protocol1> (...);
break;
case PROTOCOL_2:
doProtWork<Protocol2> (...);
break;
case PROTOCOL_3:
doProtWork<Protocol3> (...);
break;
};
}
To add a new protocol (that uses existing types) you only need to do define the protocl sequence:
typedef vector<read_int, write_int, write_int, write_int> Protocol4;
And then add a new entry to the switch statement.

I'd tend towards to using different classes to implement adapters for the different protocols to the same interface.
Depending on the protocol and the differences, you might get some gain using TMP for state machines or protocol details, but generating six sets of whatever code uses the six protocol versions is probably not worth it; runtime polymorphism is sufficient, and for most cases TCP IO is probably slow enough not to want to hard code everything.

maybe oversimplified, but this sounds like a job for inheritance?
A base class IProtocol which defines what a protocol does (and possibly some common methods), and then one implementation for IProtocol for each protocol you have?

You need a protocol factory that returns a protocol handler for the appropraite version:
ProtocolHandler& handler = ProtocolFactory::getProtocolHandler(clientProtocolVersion);
You can then use inheritance to only update the parts of the protocol that have been changed between versions.
Notice that the ProtocolHandler (base class) is basically a stratergy pattern. As such it should not maitain its own state (if required that is passed in via the methods and the stratergy will update the property of the state object).
Becuase the stratergy does not maintain state we can share the ProtcolHandler between any number of threads and as such the ownership does not need to leave the factory object. Thus the factory just need to create one handler object for each protocol version it understands (this can even be done lazily). Becuase the factory object retains ownership you can return a reference of the Protocol Handler.

I'm gonna agree with Pete Kirkham, I think it would be pretty unpleasant to maintain potentially 6 different versions of the classes to support the different protocol versions. If you can do it, it seems like it would be better to have the older versions just implement adapters to translate to the latest protocol so you only have one working protocol to maintain. You could still use an inheritance hierarchy like shown above, but the older protocol implementations just do an adaption then call the newest protocol.

Related

Why is there no asio::ssl::iostream? (and how to implement it)

I'am currently exploring the Asio library and have working code for regular TCP connections. I used asio::ip::tcp::iostream objects since stuff I want to transmit already can serialize to/deserialize from iostreams, so this was really handy and worked well for me.
I then tried to switch to SSL connections and that's when everything turned crazy. There is apparently no built-in support to get the same iostream interface that all other protocols support for a secured connection. From a design perspective this is really perplexing to me. Is there any reason why this is the case?
I am aware of the discussion in How to create a boost ssl iostream? which concludes with a wrapper class to provide iostream functionality using boost. Apart from that, according to a comment, the implementation is flawed, this also does not give the same interface as for the other protocols (a basic_socket_iostream) which also allows to e.g., set expiration times and close the connection. (I am also using asio in the non-boost version and want to avoid adding boost as an additional dependency if possible).
So, I guess my questions are:
What exactly would I need to implement to get a basic_socket_iostream for an SSL connection? I assume it would be a derivation of asio::basic_streambuf or asio::basic_socket_streambuf but I somehow can't figure out how they work and need to be tweaked.. there's just a bunch of weird pointer movement and buffer allocations and documentation to me is quite unclear on what happens when exactly to achieve what...
Why is this not already present in the first place? It seems very unreasonable to have this one protocol behave entirely different from any other and thus require major refactoring for changing a tcp::iostream based project to support secured connections
> Well, the problem I have is that the ssl::stream really does neither: I doesn't give a socket but it also doesn't give me a stream interface that would be compatible to those available from the other protocols and, yes, in that sense it behaves very differently from the others (for no apparent reason)
I don't think the stream behaves any differently from the other protocols (see
https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/overview/core/streams.html):
Streams, Short Reads and Short Writes
Many I/O objects in Boost.Asio are stream-oriented. This means that:
There are no message boundaries. The data being transferred is a continuous sequence of bytes.
Read or write operations may transfer fewer bytes than requested. This is referred to as a short read or short write.
Objects that provide stream-oriented I/O model one or more of the following type requirements:
SyncReadStream, where synchronous read operations are performed using a member function called read_some().
AsyncReadStream, where asynchronous read operations are performed using a member function called async_read_some().
SyncWriteStream, where synchronous write operations are performed using a member function called write_some().
AsyncWriteStream, where synchronous write operations are performed using a member function called async_write_some().
Examples of stream-oriented I/O objects include ip::tcp::socket, ssl::stream<>, posix::stream_descriptor, windows::stream_handle, etc.
Perhaps the confusion is that you're comparing to the iostream interface, which is simply not the same concept (it comes from the standard library).
To the question how you could make a iostream compatible stream wrapper for the ssl stream, I cannot devise an answer without consulting the documentations more and using a compiler, which I don't have on hand at the moment.
I think there is room for improvement in the library here. If you read the ip::tcp::iostream class (i.e. basic_socket_iostream<ip::tcp>), you'll see that it has two base classes:
private detail::socket_iostream_base<ip::tcp>
public std::basic_iostream<char>
The former contains a basic_socket_streambuf<ip::tcp> (a derived class of std::streambuf and basic_socket<ip::tcp>), whose address is passed to the latter at construction-time.
For the most part, basic_socket_streambuf<ip::tcp> performs the actual socket operations via its basic_socket<ip::tcp> base class. However, there is the connect_to_endpoints() member function that jumps the abstraction and calls several low-level functions from the detail::socket_ops namespace directly on socket().native_handle(). (This seems to have been introduced in Git commit b60e92b13e.) Those functions will only work on TCP sockets, even though the class is a template for any protocol.
Until I discovered this issue, my plan to integrate SSL support as a iostream/streambuf was to provide a ssl protocol class and a basic_socket<ssl> template specialization to wrap the existing ssl::context and ssl::stream<ip::tcp::socket> classes. Something like this (won't compile):
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/basic_socket.hpp>
#include <boost/asio/ssl.hpp>
namespace boost {
namespace asio {
namespace ip {
class ssl
: public tcp // for reuse (I'm lazy!)
{
public:
typedef basic_socket_iostream<ssl> iostream;
// more things as needed ...
};
} // namespace ip
template <>
class basic_socket<ip::ssl>
{
class SslContext
{
ssl::context ctx;
public:
SslContext() : ctx(ssl::context::sslv23_client)
{
ctx.set_options(ssl::context::default_workarounds);
ctx.set_default_verify_paths();
}
ssl::context & context() { return ctx; }
} sslContext;
ssl::stream<ip::tcp::socket> sslSocket;
public:
explicit basic_socket(const executor & ex)
: sslSocket(ex, sslContext.context())
{}
executor get_executor() noexcept
{
return sslSocket.lowest_layer().get_executor();
}
void connect(const ip::tcp::endpoint & endpoint_)
{
sslSocket.next_layer().connect(endpoint_);
sslSocket.lowest_layer().set_option(ip::tcp::no_delay(true));
sslSocket.set_verify_mode(ssl::verify_peer);
sslSocket.set_verify_callback(
ssl::rfc2818_verification("TODO: pass the domain here through the stream/streambuf somehow"));
sslSocket.handshake(ssl::stream<ip::tcp::socket>::client);
}
void close()
{
sslSocket.shutdown();
sslSocket.next_layer().close();
}
};
} // namespace asio
} // namespace boost
But due to the design issue I'll have to specialize basic_socket_streambuf<ip::ssl> as well, to avoid the detail::socket_ops routines. (I should also avoid injecting the ssl protocol class into the boost::asio::ip namespace, but that's a side concern.)
Haven't spent much time on this, but it seems doable. Fixing basic_socket_streambuf<>::connect_to_endpoints() first should help greatly.

Don't break backwards-compatibility messages in a DDS system

I'm involved in a project which uses DDS as a protocol to communicate and C++ as language. As you know exchanged messages are called Topics. Well, sometimes a team must change a topic definition, as a consequence of this, other software which depends on this topic stops working, it is necessary to update the topic everywhere and recompile. So, my question is, do you know how not to break backwards compatiblity? I have been searching and I have found google protocol buffer, they say this:
"You can add new fields to your message formats without breaking
backwards-compatibility; old binaries simply ignore the new field when
parsing. So if you have a communications protocol that uses protocol
buffers as its data format, you can extend your protocol without
having to worry about breaking existing code."
Any other idea?
Thanks in advance.
The OMG specification Extensible and Dynamic Topic Types for DDS (or DDS-XTypes for short) addresses your problem. Quoted from that specification:
The Type System supports type evolution so that it is possible to
“evolve the type” as described above and retain interoperability
between components that use different versions of the type
Not all DDS implementations currently support XTypes, so you might have to resort to a different solution. For example, you could include a version numbering scheme in your Topic name to avoid typing conflicts between different components. In order to make sure every component receives the right data it needs, you can create a service that has the responsibility to forward between different versions of the Topics as needed. This service has to be aware of the different versions of the Topics and should take care of filling default values and/or transform between their different types. Whether or not this is a viable solution depends, among other things, on your system requirements.
The use of a different type system like Protocol Buffers inside DDS is not recommended if it is just for the purpose of solving the type evolution problem. You would essentially be transporting your PB messages as data opaque to the DDS middleware. This means that you would also lose some of the nice tooling features like dynamic discovery and display of types, because the PB messages are not understood by the DDS middleware. Also, your applications would become more complex because they would be responsible for invoking the right PB de/serialization methods. It is easier to let DDS take care of all that.
Whichever route you take, it is recommended that you manage your data model evolution tightly. If you let just anybody add or remove some attributes to their liking, the situation will become difficult to maintain quickly.
The level of support for protocol buffers with DDS depends on how its implemented. With PrismTech's Vortex for instance, you don't lose content-awareness, nor dynamic discovery nor display of types as both the middleware and its tools DO understand the PB messages. W.r.t. 'populating' your PB-based topic, thats conform the PB-standard and generated transparently by the protoc compiler, so one could state that if you're familiar with protobuf (and perhaps not with OMG's IDL alternative), then you can really benefit from a proper integration of DDS and GPB that retains the global-dataspace advantages yet i.c.w. a well-known/popular type-system (GPB)
You could try wrapping the DDS layer in your own communication layer.
Define a set of DDS Types and DDS Profiles that covers all your needs.
Then each topic will be define as one of those types associated to one of those profiles.
For example you could have a type for strings, and a binary type.
If you use the same compiler for all your software, you can even safely memcopy a C struct to the binary type.
module atsevents {
valuetype DataType {
public string<128> mDataId;
public boolean mCustomField1;
public boolean mCustomField2;
};
valuetype StringDataType : DataType {
public string<128> mDataKey; //#key
public string<1024> mData;
};
valuetype BinaryDataType : DataType {
public string<128> mDataKey; //#key
public sequence<octet, 1024> mData;
public unsigned long mTypeHash;
}
}

Which is the design pattern for easy maintanace of backward compatibility of data files?

We develop a desktop application which goes into versions. We save the data in a file (which has version in which it is saved) and open it lateron to work on it. Since our data model keeps changing over versions, we keep adding APIs like upgrade_from_ver1_to_ver2. This is getting chiotic, since we are now in version 10.0. We also slowly stop supporting older versions (say files saved before 4.0). Can you suggest a better way or a design pattern to save and provide backward compatibility?
It will be good if this can be designed as a seperate utility, called during import/opening of data files. It shall also be called seperately just to get current data model from old data files.
How about you just implement an upgrade function from the version before to the new version? Then you can chain those together to upgrade from an older version.
This seems to me the minimal implementation effort, while always allowing to upgrades from any prior version. The downside is, that you might loose information in the conversion chain, e.g. if the possible values of one property is reduced in one version and later extended again.
The first important thing to do is to have a separate class(es) for saving your data so your data objects aren't burdened with the responsibility of saving.
Next I would use the template pattern, defining the basics of your read/open operation. Your AbstractReader class might just define a single Read method. You could then create classes inheriting from the AbstractReader - potentially one for each file version. You could then use the abstract factory pattern to detect the version of file being opened, then return the corresponding Reader implementation for that version.
You can use ParallelChange Design Pattern here.
To give you a little bit idea, this pattern has 3 phases expand, migrate, and contract.
Let say you have method1 in your file and you want to update the method structure.
public void method1(Obj1 obj1) {
...
}
1. Expand
Now you need to support different data structure with backward compatibility. Then add new method keeping the old one as is.
public void method1(Obj1 obj1) {
...
}
public void method1(Obj2 obj2) {
...
}
After this release, all your clients will still use the method1(Obj1 obj1). You have just added capability to call a different method which will use now onwards.
2. Migrate
In this phase, you need to upgrade your all clients to new method structure ie method1(Obj2 obj2). Sometimes we need to force the client to use the new method structure and sometimes the client moves gradually. We generally stop to give new features to deprecated methods. Like after the first Expand, we found a bug in method1(Obj1 obj1), we say this will be solved in method1(Obj2 obj2).
3. Contract
Once your all clients are moved, it's totally safe to remove your method1(Obj1 obj1) implementation.
Eventually, you will upgrade your system from version to version with backward compatibility.
The only downfall that I personally feel is, it will take at least 3 release before you remove your whole code. But without a doubt, this would be surely hassle-free.

Design methods for multiple serialization targets/formats (not versions)

Whether as members, whether perhaps static, separate namespaces, via friend-s, via overloads even, or any other C++ language feature...
When facing the problem of supporting multiple/varying formats, maybe protocols or any other kind of targets for your types, what was the most flexible and maintainable approach?
Were there any conventions or clear cut winners?
A brief note why a particular approach helped would be great.
Thanks.
[ ProtoBufs like suggestions should not cut it for an upvote, no matter how flexible that particular impl might be :) ]
Reading through the already posted responses, I can only agree with a middle-tier approach.
Basically, in your original problem you have 2 distinct hierarchies:
n classes
m protocols
The naive use of a Visitor pattern (as much as I like it) will only lead to n*m methods... which is really gross and a gateway towards maintenance nightmare. I suppose you already noted it otherwise you would not ask!
The "obvious" target approach is to go for a n+m solution, where the 2 hierarchies are clearly separated. This of course introduces a middle-tier.
The idea is thus ObjectA -> MiddleTier -> Protocol1.
Basically, that's what Protocol Buffers does, though their problematic is different (from one language to another via a protocol).
It may be quite difficult to work out the middle-tier:
Performance issues: a "translation" phase add some overhead, and here you go from 1 to 2, this can be mitigated though, but you will have to work on it.
Compatibility issues: some protocols do not support recursion for example (xml or json do, edifact does not), so you may have to settle for a least-common approach or to work out ways of emulating such behaviors.
Personally, I would go for "reimplementing" the JSON language (which is extremely simple) into a C++ hierarchy:
int
strings
lists
dictionaries
Applying the Composite pattern to combine them.
Of course, that is the first step only. Now you have a framework, but you don't have your messages.
You should be able to specify a message in term of primitives (and really think about versionning right now, it's too late once you need another version). Note that the two approaches are valid:
In-code specification: your message is composed of primitives / other messages
Using a code generation script: this seems overkill there, but... for the sake of completion I thought I would mention it as I don't know how many messages you really need :)
On to the implementation:
Herb Sutter and Andrei Alexandrescu said in their C++ Coding Standards
Prefer non-member non-friend methods
This applies really well to the MiddleTier -> Protocol step > creates a Protocol1 class and then you can have:
Protocol1 myProtocol;
myProtocol << myMiddleTierMessage;
The use of operator<< for this kind of operation is well-known and very common. Furthermore, it gives you a very flexible approach: not all messages are required to implement all protocols.
The drawback is that it won't work for a dynamic choice of the output protocol. In this case, you might want to use a more flexible approach. After having tried various solutions, I settled for using a Strategy pattern with compile-time registration.
The idea is that I use a Singleton which holds a number of Functor objects. Each object is registered (in this case) for a particular Message - Protocol combination. This works pretty well in this situation.
Finally, for the BOM -> MiddleTier step, I would say that a particular instance of a Message should know how to build itself and should require the necessary objects as part of its constructor.
That of course only works if your messages are quite simple and may only be built from few combination of objects. If not, you might want a relatively empty constructor and various setters, but the first approach is usually sufficient.
Putting it all together.
// 1 - Your BOM
class Foo {};
class Bar {};
// 2 - Message class: GetUp
class GetUp
{
typedef enum {} State;
State m_state;
};
// 3 - Protocl class: SuperProt
class SuperProt: public Protocol
{
};
// 4 - GetUp to SuperProt serializer
class GetUp2SuperProt: public Serializer
{
};
// 5 - Let's use it
Foo foo;
Bar bar;
SuperProt sp;
GetUp getUp = GetUp(foo,bar);
MyMessage2ProtBase.serialize(sp, getUp); // use GetUp2SuperProt inside
If you need many output formats for many classes, I would try to make it a n + m problem instead of an n * m problem. The first way I come to think of is to have the classes reductible to some kind of dictionary, and then have a method to serlalize those dictionarys to each output formats.
Assuming you have full access to the classes that must be serialized. You need to add some form of reflection to the classes (probably including an abstract factory). There are two ways to do this: 1) a common base class or 2) a "traits" struct. Then you can write your encoders/decoders in relation to the base class/traits struct.
Alternatively, you could require that the class provide a function to export itself to a container of boost::any and provide a constructor that takes a boost::any container as its only parameter. It should be simple to write a serialization function to many different formats if your source data is stored in a map of boost::any objects.
That's two ways I might approach this. It would depend highly on the similarity of the classes to be serialized and on the diversity of target formats which of the above methods I would choose.
I used OpenH323 (famous enough for VoIP developers) library for long enough term to build number of application related to VoIP starting from low density answering machine and up to 32xE1 border controller. Of course it had major rework so I knew almost anything about this library that days.
Inside this library was tool (ASNparser) which converted ASN.1 definitions into container classes. Also there was framework which allowed serialization / de-serialization of these containers using higher layer abstractions. Note they are auto-generated. They supported several encoding protocols (BER,PER,XER) for ASN.1 with very complex ASN sntax and good-enough performance.
What was nice?
Auto-generated container classes which were suitable enough for clear logic implementation.
I managed to rework whole container layer under ASN objects hierarchy without almost any modification for upper layers.
It was relatively easy to do refactoring (performance) for debug features of that ASN classes (I understand, authors didn't intended to expect 20xE1 calls signalling to be logged online).
What was not suitable?
Non-STL library with lazy copy under this. Refactored by speed but I'd like to have STL compatibility there (at least that time).
You can find Wiki page of all the project here. You should focus only on PTlib component, ASN parser sources, ASN classes hierarchy / encoding / decoding policies hierarchy.
By the way,look around "Bridge" design pattern, it might be useful.
Feel free to comment questions if something seen to be strange / not enough / not that you requested actuall.

How would you implement Erlang-like send and receive in C++?

Actually, this question seems to have two parts:
How to implement pattern matching?
How to implement send and receive (i.e. the Actor model)?
For the pattern matching part, I've been looking into various projects like App and Prop. These look pretty nice, but couldn't get them to work on a recent version (4.x) of g++. The Felix language also seems to support pattern matching pretty well, but isn't really C++.
As for the Actor model, there are existing implementations like ACT++ and Theron, but I couldn't find anything but papers on the former, and the latter is single-threaded only [see answers].
Personally, I've implemented actors using threading and a thread-safe message queue. Messages are hash-like structures, and used these together with a number of preprocessor macros to implemented simple pattern matching.
Right now, I can use the following code to send a message:
(new Message(this))
->set("foo", "bar")
->set("baz", 123)
->send(recipient);
And the following to do simple pattern matching (qDebug and qPrintable are Qt-specific):
receive_and_match(m)
match_key("foo") { qDebug("foo: %s", qPrintable(m->value("foo").toString())); }
or_match_key("baz") { qDebug("baz: %d", m->value("baz").toInt()); }
or_match_ignore
end_receive
However, this looks a bit hackish to me, and isn't very robust.
How would you do it? Did I miss any existing work?
As for the Actor model, there are
existing implementations like ACT++
and Theron, but I couldn't find
anything but papers on the former, and
the latter is single-threaded only.
As the author of Theron, I was curious why you believe it's single-threaded?
Personally, I've implemented actors
using threading and a thread-safe
message queue
That's how Theron is implemented.. :-)
Ash
One of the important things about erlang is how the features are used to make robust systems.
The send/recieve model is no-sharing, and explicitly copying.
The processes themselves are lightweight threads.
If you did desire the robust properties of the erlang model, you would be best to use real processes and IPC rather than threads.
If you want robust message passing though you may end up wanting to serialize and deserialise the contents. Especially with type safety.
Pattern matching in C++ isn't always pretty but there will be a good pattern for this - you will end up creating a dispatcher object that uses some form of polymorphism to get what you want.
Although if you are not careful you end up with xml over pipes :)
Really, if you want the erlang model you really want to use erlang. If there are slow bits, I'm sure you can augment your program using a foreign function internet.
The problem about re-implementing parts, is you won't get a good cohesive library and solution. The solutions you have already don't look much like C++ anymore.
I'm currently implementing an actor library for C++ called "acedia" (there's nothing yet about it on google) that uses "type matching". The library is a project for my master thesis and you can send any kind of data to an actor with it.
A small snippet:
recipient.send(23, 12.23f);
And on the recipient side you can either analyze the received message like this:
Message msg = receive();
if (msg.match<int, float>() { ... }
... or you can define a rule set that invokes a function or method for you:
void doSomething(int, float);
InvokeRuleSet irs;
irs.add(on<int, float>() >> doSomething);
receiveAndInvoke(irs);
It's also possible to match both on type and on value:
Message msg = receive();
if (msg.match<int, float>(42, WILDCARD) { ... }
else if (msg.match<int, float>() { ... }
The constant "WILDCARD" means, that any value will be acceptet. Pass no arguments is equal set all arguments to "WILDCARD"; meaning that you only want to match the types.
This is certainly a small snippet. Also you can use "case classes" like in Scala. They are comparable to "atomics" in erlang. Here is a more detailed example:
ACEDIA_DECLARE_CASE_CLASS(ShutdownMessage)
ACEDIA_DECLARE_CASE_CLASS(Event1)
ACEDIA_DECLARE_CASE_CLASS(Event2)
To react to the defined case classes you can write an actor like this:
class SomeActor : public Actor
{
void shutdown() { done = true; }
void handleEvent1();
void handleEvent1();
public:
SomeActor() : done(false) { }
virtual void act()
{
InvokeRuleSet irs;
irs
.add(on<ShutdownMessage>() >> method(&SomeActor::shutdown))
.add(on<Event1>() >> method(&SomeActor::handleEvent1))
.add(on<Event2>() >> method(&SomeActor::handleEvent2))
;
while (!done) receiveAndInvoke(irs);
}
};
To create a new actor and start it, all you have to write is:
Acedia::spawn<SomeActor>();
Although the library not even reached beta stadium the shown snippets work and i have a first application running on it. One major goal of the library is to support distributed programming (also across a network).
Your question is a while ago, but if you're interested in it: let me know! :)
You can mimic the behavior using Qt's signal/slot mechanism, especially since Qt's signal/slot supports multithread.
I would definitely be interested in looking at your "acedia" library and would love to help in any way that I could. Erlang has some wonderful constructs and C++ could definitely benefit from such a library.
Today I hostet the library at sourceforge: https://sourceforge.net/projects/acedia/
As I said before it's an early release. But feel free to critique it!
Today, if you want erlang style robust actors in C++, and pattern matching,
maybe Rust is the answer.
Of course this wasn't around publically when the OP asked ~5years ago, and as of april 2014 it still isn't v1.0 yet - but its been progressing very well and is definitely stabilizing, enough of the language core is stable I think.
And ok its not C++, but it has the same approach to memory management as C++, except that it supports lightweight tasks with no shared memory by default (then provides controlled library features for sharing - "Arc");
It can directly call (and directly expose) 'extern C' functions. You can't share templated library headers with C++ - but you can write generics that mimick C++ collection classes (and vica versa) to pass references to data-structures across.