Expensive time of day!

I recently read
an article about Template Metaprogramming in C ++. And there was such a
comment there : “Exactly the same thing with the same level of customizability could be done on interfaces, implementations, factories, defines, configs and even a whole lot of things.” And in general, the moral of the article and discussion - these templates from Alexandrescu are not very necessary in life.
I remembered my task, where his (Alexandrescu) idea about orthogonal design helped me a lot. I want to share it with you.
Problem
I write programs for computer and microcontroller devices. And often I have to organize a connection between them. And, as usual, in programs it is necessary to catch errors. And if the PC program is not very difficult to debug, then with microcontrollers things are much worse (in the sense, there are all sorts of in-circuit debuggers, but they are not suitable for real-time tasks). Well, and even not in this case - sometimes you need to debug the connection itself! That is, it seems that the information should go, but it goes somehow wrong ... This is essential at the beginning of the program development - when the connection itself, the protocol itself, etc. is being debugged. I was looking for some program to track the USB exchange, yes somehow could not find. Something caught my eye, but it was all not that (or that, but very paid).
And suddenly I thought - why should I not write such a program myself? As a matter of fact, and all business - on the function of reading / writing, opening / closing, we hang up the additional function of writing to an external file. And there with the help of #define we disable these functions when they are not needed.
')
In parallel with this, I made several useful classes for exchanging data in memory between processes, over local and global networks. And, too, it was necessary to debug this exchange.
Both here and there I used a common logical protocol - there is a forbidden symbol in the message, which is a sign of the start / end of the packet.
And so, I come across this book Alexandrescu. A bell rang in my head that something could be stirred up here ...
Strictly speaking, I wanted to make a single base class that implements this very logical protocol. This class also needs to specify the physical nature of the exchange — USB, interprocess exchange (it was in Windows, memory mapping was used), exchange over a local network (named pipes), global (sockets). I also wanted to make a debug function. And I also wanted to ...
Formulation of the problem
So, we need to make a base class that has the following properties (or “policies”):
- physical nature - what method of communication is used. Now I have FTDI USB sharing, memory mapping, named pipes, sockets. I would also like to add an exchange on the COM port;
- The processing method - at the beginning it was just the functions “is there an input?”, “There is an output”, “what is the communication status?”. Then, when I wrote a program for intensive asynchronous exchange, I implemented a multitasking exchange algorithm. So, I now have 2 different policies for the method of calling - functional and multi-threaded;
- Exchange protection - do not forget that my program works in a multitasking environment and different threads can simultaneously access the exchange device. And maybe not. Therefore, we obtain another “plane” - a single-task (in simple cases) protection by critical sections. If necessary, I will make the protection with mutexes, but for the time being there was no need;
- Report to file - need or not? If necessary - in what form? I made an option that writes a delimited text file. It is conveniently exported to Excel and other similar programs;
- Measurement of time - sometimes it is important to know the time intervals between packets. They can be measured differently (depending on the task). I made a rough measurement of time (GetTicksCount), accurate (high-precision timer in Windows) and ultra-precise ( RDTSC timer).
And most importantly, only the first two essential policies are essential - the nature of communication and the way of handling communication events. Everything else can be ignored. Therefore, I have an “empty” policy - one that does nothing.
Yes, one more thing - this class does not pretend to the universality of the solution. We are not talking about the dynamic connection of different methods and classes. All global settings should be implemented in one place of the program - and henceforth for the whole time of work does not change.
What happened
As a result, InterConnect template class was made:
template < typename Phys,
In the body of the class, I check everywhere that the policy is being used or not. Here is how, for example, the byte writing function is implemented:
UPD - there were exceptions, removed template <typename Phys, typename Call, typename Prot, typename Log, typename Timer> bool InterConnectStorage<Phys,Call,Prot,Log,Timer>::send_byte (const unsigned char Val) { byte val; bool res = true; Prot::ProtectByte pr (_prot);
Everything seems to be obvious here. From the "protection" policy, we call the byte protection function (in other places the entire packet is protected). We call the call function from the “organization of the exchange” policy (we transfer the object “physical nature” and, in fact, the byte) to it. Then we call the “record report” policy to check whether there is this policy? If yes, write to the report information. If the “countdown” policy is used, we write with due account for time (we set the “event” mark with the stamp function, we write to the report), no — just to the report.
Everywhere there is a check - is this policy used or not? The static function bool to_use (void) answers this question. Due to the fact that it is static and very simple, the compiler can optimize the code - either not to create it at all (if the policy is not used) or to call it immediately without checking.
As a result, a rather cumbersome C ++ code turns into a fairly simple and optimal assembly language.
Using
As a result, I got a very handy tool that is configured by a very small code.
Here is an example of a small USB exchange program:
typedef InterConnectStrat::FTDIAccess Phys;
With the MakeLogFile constant, I completely change the logic of the program. Recompilation, of course, takes time, but the resulting code is very efficient, both in speed and resources, and in functionality.
In this code, the intervals between the shipments are not important to me, but at the beginning of the development of this program it was necessary. I changed only typedef / * ... * / Timer - and there I set everything.
Along the way, I was working out the reaction of the program to messages. For this, I changed the nature of the exchange from USB to interprocess. I made a second console simple application that sent the packets I needed. And so I set up the reaction of the main program to the desired one. Then I, again, changed the typedef Phys and setting the communication parameters in one place of the program (5 lines maximum) - and everything began to work with USB. Very easy and convenient!
I also overloaded the main operations - the beginning / end of the packet (this sets, if necessary, the protection mode in a multitasking environment), the sending of bytes, etc. As a result, the packet entry looks like this:
The code speaks for itself.
Rate the beauty of the solution! In the whole program, except for the initialization point, I am indifferent to a whole bunch of things - what exchange protocol, whether the program is multitask or not, is there an entry in the report or not. Listing is very simple!
But at the same time, unlike all kinds of factories of classes, etc., the resulting code is optimal - there are no unnecessary checks and transitions.
Comparison of other methods
And now for interest, we will consider other ways of solving the same problem with C ++ 9x. Prompt in the comments that I missed:
- Inheritance - unlike templates, guarantees the existence of the necessary functions (which the templates lack). But everything else - a dynamic connection, various virtual functions - is not needed here. Moreover, virtual functions cannot be optimized in embedded ones. There is an undoubted bonus for templates;
- Factories - I have not come across them yet. As I understand it, this is a further development of the previous method. In terms of optimization and speed, it is inferior to templates;
- typedef - probably, all this could be implemented without templates. But the .h file of templates can be compiled without the classes used in it, with a typedef this number will not work. And somehow it will be very difficult, on typedef :-);
- #define - ahem ... I think no comment ;-)
In general, the tool turned out very convenient and high quality. It allows you to develop your functionality in completely different planes.
PS Alexandrescu - well done, that some would not say!