📜 ⬆️ ⬇️

About poor Aleksandresku I put in a word

Expensive time of day!
Book Alexandrescu 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”):


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, // physical layer of connection - shared memory, USB, RS-232, LAN... typename Call, // call mode - function style, thread with events typename Prot = NoStrategy, // protection layer - protection based on critical sections, multiprocess protection etc. typename Log = NoStrategy, // logging mode - no logging (default), to file, to external process typename Timer = NoStrategy // timer mode - no timer (default), based on GetTickCount, high frequency timer, RDTSC > class InterConnect { protected: Phys _phys; Call _call; Prot _prot; Log _log; Timer _timer; // ... }; 


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); // ... res &= _call.send (_phys, Val); // ... if (Log::to_use ()) { if (Timer::to_use ()) { _timer.stamp (); _log.send (Val, _timer.dprev (), _timer.dstart (), res); } else _log.send (Val, 0, 0, res); return res; } } 


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; //   typedef InterConnectStrat::ThreadCall <Phys> Call; //   typedef InterConnectStrat::CritSectProtectPack Prot; //   typedef InterConnectStrat::NoStrategy Timer; //    #if defined (MakeLogFile) typedef InterConnectStrat::TabulatedASCIILogFile Log; //    File _log; //   #else typedef InterConnectStrat::NoStrategy Log; //    #endif InterConnectStorage <Phys, Call, Prot, Log, Timer> _con; //    


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:

 // .   _con++; _con <<= SetProgramDate; _con <<= _HEX_date.get_Unix_time (); --_con; // .     _con++; _con <<= GotoProgram; --_con; 


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:



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!

Source: https://habr.com/ru/post/130465/


All Articles