Hello. I am writing software for embedded systems. I started to switch from C to C ++. Developing libraries on c ++, I noticed that part of the code in each of my libraries is repeated, namely, part of the binding of each library to the total non-volatile memory and energy dependent memory available from outside. As a result, a common table of data available via the ModBus protocol and so on is formed.

Thus, by adding a library to a project, its data becomes available for modification and reading via ModBus.

The question is, how is it better to organize the code so that you do not need to write the same code for each new library?

I started by creating a base class for all libraries in which functions common to all libraries are described, and each of the libraries inheriting the base class describes its own particular virtual methods. It turned out that all created objects of any class inherited from the base class are placed in a common queue, in which their virtual functions are called in Main.

However, I feel that I am doing wrong and there is a better way. Tell me how best to implement this task?

Closed due to the fact that it is necessary to reformulate the question so that it was possible to give an objectively correct answer by the participants Vlad from Moscow , Abyx , Alexey Shimansky , αλεχολυτ , Harry 12 May '17 at 6:09 .

The question gives rise to endless debates and discussions based not on knowledge, but on opinions. To get an answer, rephrase your question so that it can be given an unambiguously correct answer, or delete the question altogether. If the question can be reformulated according to the rules set out in the certificate , edit it .

  • If possible, add your code, it will be better to understand your difficulties. - 0xdb

2 answers 2

The question is, how is it better to organize the code so that you do not need to write the same code for each new library?

Design options, as a rule, there are many. Considering that C ++ is used, it is advisable to use some of the OOP "good tone" rules. In your case - "sharing responsibility." As I understood from the statement of the question, "binding" is not the main task of libraries. So move this functionality into a separate class (for example, the "factory" pattern). If the "binding" is a global functionality, it makes sense to use the "singleton" pattern.

At one time he experimented with something like that, he wrote a test example of the “singleton factory”. Look, maybe you will find something useful for yourself. In extreme cases - you will not find)

#include <iostream> #include <memory> //////////////////////////////////////////////////////////////////////////////////////// template<typename T> struct Holder { Holder() { std::cout << "Holder()::Holder()" << std::endl; if (!Self) Self = new T(); Self->Init(); } ~Holder() { if (Self) { Self->Cleanup(); delete Self; std::cout << "~Holder()::Holder()" << std::endl; } } T* Self = nullptr; }; //////////////////////////////////////////////////////////////////////////////////////// template <typename T> class Singleton { public: static T* Instance() { std::cout << "Singlton::Instance()" << std::endl; static Holder<T> Dummy; return Dummy.Self; } private: Singleton() = delete; Singleton(Singleton const&) = delete; Singleton& operator= (Singleton const&) = delete; Singleton(Singleton const&&) = delete; Singleton& operator= (Singleton const&&) = delete; }; //////////////////////////////////////////////////////////////////////////////////////// class Base { public: virtual void Init() { std::cout << "Base::Init()" << std::endl; } virtual void Cleanup() { std::cout << "Base::Cleanup()" << std::endl; } }; //////////////////////////////////////////////////////////////////////////////////////// class Config: public Base { public: int i = 0; void Init() override { std::cout << "Config::Init()" << std::endl; } }; //////////////////////////////////////////////////////////////////////////////////////// class ConfigDerived: public Config { public: int j = 0; void Cleanup() override { std::cout << "ConfigDerived::Cleanup()" << std::endl; } }; //////////////////////////////////////////////////////////////////////////////////////// int main() { std::cout << "===================================" << std::endl; auto R1 = Singleton<Config>::Instance(); auto R2 = Singleton<ConfigDerived>::Instance(); std::cout << "R1->i: " << R1->i << std::endl; R1->i = 7; std::cout << "R2->i: " << R2->i << std::endl; std::cout << "===================================" << std::endl; R2->i = 3; auto XX = Singleton<ConfigDerived>::Instance(); std::cout << "XX->i: " << XX->i << std::endl; std::cout << "===================================" << std::endl; return 0; } 
  • in some cases, singletons are antipatterns. What if the interface is multithreaded / multisession? - Swift
  • In my code, this is taken into account (read about static). At first, I myself suffered from mutexes. As it turned out - everything is much easier. - Majestio
  • Thanks for your reply! Yes, binding is not the main task of libraries, the functionality in each of them is generally different. There will be many separate objects for each class. For example, I have a class of accidents, to create objects - specific accidents. There is a maintenance class for building specific maintenance objects (bearing replacement, oil change). It is necessary to somehow make out the common code for these classes, associated with binding to the address space of the entire device, in order not to write it again. Thanks for the tip-off to the class factory, I'll dig in that direction! - Ilia

In fact, you write your own API, this is a possible option.

In principle, there are three ways out:

  1. Write a low level API using a "C" binding, and two wrappers for each language. The disadvantage is that you have to debug both wrappers. Advantage - in some cases it is easier to maintain if the changes you make affect only part of the API, but not the interfaces.
  2. Write a complete C library, debug, write a wrapper for C ++. The question is - is a wrap needed at all and for what?
  3. Create a wrapper from "pseudo-classes", so to speak, emulate Simula-like OPP in C. You need to be careful not to allow UB.

Oddly enough, the last option sounds, in fact, it is quite common. in various banking and industrial software.