-Everything is as usual. What is the problem?
SyncBoard
for the first system only overrides the speed request method, while new methods are added for the second system. Thus, the caller needs knowledge of the specific type of each device with which he works. But the caller does not change from system to system. Its basic logic is unchanged, only ways of interacting with devices change.- Of course, if the caller needs to use the new functionality of the device, then he simply must know its specific type.
SyncBoard_System1
, and in the second, respectively, SyncBoard_System2
. As I said above, this module / class does not change its logic in these two systems. Changing only the interaction with the device. What are the options?-Thank you, laughed.
- Already better. Only you on each device with which the class interacts will be done by a virtual function? With new devices, new virtual functions will have to appear in interested classes.
-Sounds like in smart books. But for the new functionality of the device, the strategy must also implement it. That is, the strategy interface will change, and then you still need to know the specific implementation (type) of the strategy.
- Very similar to Mediator or Facade.
-And if you have more than one temperature sensor in your system? Function getTemperature()
what will return?
-Not beautiful, but not deadly.
- You have not forgotten about the different parameters of the functions and different return values?
class IDevData { public: IDevData(); virtual ~IDevData(); virtual int getID() = 0; }; class DevManager_v2 : public IDeviceManager { public: bool initialize() { // } // virtual void callMethod(int dev_id, IDevData& data) { switch (data.getID()) { case DATA_SYNC_BOARD_GET_STATE: // dev_id getState(); // IDevData break; case DATA_SYNC_BOARD_GET_VELOCITY: // ... break; // ... etc } } };
callMethod
method and ...-Yes Yes! And the specific type ofIDevData
for each call. If you ran earlier from binding to specific implementations of devices, then you came to linking to specific implementations ofIDevData
wrappers. Funny.
IDevData
create. And how does this differ from the situation where the caller knew a particular type of device? Yes, nothing!callMethod()
. But the problem with the transfer and return of parameters reduced all efforts to nothing. It would be great if we could pass any parameters in any quantity to this single function and could get any type of return value from it ...Yes, everyone already understood what you are talking about templates and C ++ 11. Come on, tell me how the spaceships plow ... ©
callMethod
function should be template and have the following prototype: template <typename ResType, typename ... Args> ResType callMethod(int dev_id, Args&& ... args);
- And how will you solve the situation when in the same class there are two functions identical in signature? Judging by your thinking, you want to go somewhere (where?) Todev_id
(which means a particular class) and just pass all the parameters ofArgs&&…
someone (to whom?).
int method_id
, which I don’t like at all, or give a different meaning to the parameter - int dev_id
. Let's call it, say, command_id
, and now it will mean a specific method of a particular class. That is, a certain pair identifier Class-> method. Thus, the values ​​of these command_id
will be exactly the same as the methods of all classes of devices. For good, of course, this should be turned into an enumeration, but we will not dwell on this. Now, about "where to go with command_id
" and "who to send to Args&&
". The command_id
parameter gives us a hint. A certain collection of methods is supposed, which is accessed by command_id
. In other words, the following scheme is needed:callMethod
remove the required object from the storage using the command_id
key and transfer all parameters-Thanks, Cap.
class base_impl { public: virtual std::type_info const& getTypeInfo() const = 0; };
std::function
. All that is required of this class is to overload the operator()
, into which the parameters for the delegation of the call are passed. template <typename ResType, typename ... Args> class type_impl : public base_impl { typedef std::function<ResType(Args ...)> _Fn; _Fn _func; public: type_impl(std::function<ResType(Args ...)> func) : _func(func) {} std::type_info const& getTypeInfo() const { return typeid(_Fn); } ResType operator()(Args&& ... args) { return _func(std::forward<Args>(args)...); } };
base_impl
class. But how to get to the operator()
call through this pointer? A type conversion is required. For this we have a method getTypeInfo()
. To hide this underling, as well as the need to manually write each time, when adding a function to a container, the type_impl
template, create the last class with one little trick - the template designer. class FuncWrapper { std::unique_ptr<base_impl> holder; public: template <typename ResType, typename ... Params> FuncWrapper(std::function<ResType(Params ...)> func) : holder(new type_impl<ResType, Params ...>(func)) {} ~FuncWrapper() {} template <typename ResType, typename ... Args> ResType call(Args&& ... args) { typedef std::function<ResType(Args ...)> _Fn; if (holder->getTypeInfo() != typeid(_Fn)) throw std::exception("Bad type cast"); type_impl<ResType, Args ...>* f = static_cast<type_impl<ResType, Args ...>*>(holder); return (*f)(std::forward<Args>(args)...); } } };
call()
method and in it we delegate the call to the saved type_impl
. class FuncWrapper { private: // callable object. // , class base_impl { public: virtual std::type_info const& getTypeInfo() const = 0; }; // . // - base_impl. // type_impl, // base_impl // - ResType // - Args... template <typename ResType, typename ... Args> class type_impl : public base_impl { typedef std::function<ResType(Args ...)> _Fn; _Fn _func; // ! public: // , std::function // std::function ? type_impl(std::function<ResType(Args ...)> func) : _func(func) {} // , . // , . // exception. std::type_info const& getTypeInfo() const { return typeid(_Fn); } // , , . ResType operator()(Args&& ... args) { return _func(std::forward<Args>(args)...); } }; std::unique_ptr<base_impl> holder; public: // , std::function // , template <typename ResType, typename ... Params> FuncWrapper(std::function<ResType(Params ...)> func) : holder(new type_impl<ResType, Params ...>(func)) {} ~FuncWrapper() {} // , template <typename ResType, typename ... Args> ResType call(Args&& ... args) { typedef std::function<ResType(Args ...)> _Fn; if (holder->getTypeInfo() != typeid(_Fn)) throw std::exception("Bad type cast"); // , type_impl<ResType, Args ...>* f = static_cast<type_impl<ResType, Args ...>*>(holder.get()); return (*f)(std::forward<Args>(args)...); } }; // , test, - class test { public: test() {} int fn1(int a) { cout << "test::fn1!!! " << a << endl; return ++a; } int fn2(int a, int b) { cout << "test::fn2!!! " << a << endl; return a + 2; } int fn3(int a, int b) { cout << "test::fn3!!! " << a << endl; return a + 3; } }; class IDeviceManager { protected: std::map<int, FuncWrapper*> m_funcs; public: virtual ~IDeviceManager() {}; virtual void initialize() = 0; template <typename ResType, typename ... Args> ResType callMethod(int command_id, Args&& ... args) { // – . return m_funcs[command_id]->call<ResType>(std::forward<Args>(args)...); } }; const int FN1_ID = 0; const int FN2_ID = 1; const int FN3_ID = 2; class DevManager_v3 : public IDeviceManager { std::unique_ptr<test> m_test_ptr; public: void initialize() { // m_test_ptr.reset(new test); std::function<int(int)> _func1 = std::bind(&test::fn1, m_test_ptr.get(), std::placeholders::_1); std::function<int(int, int)> _func2 = std::bind(&test::fn2, m_test_ptr.get(), std::placeholders::_1, std::placeholders::_2); std::function<int(int, int)> _func3 = std::bind(&test::fn3, m_test_ptr.get(), std::placeholders::_1, std::placeholders::_2); // m_funcs[FN1_ID] = new FuncWrapper(_func1); m_funcs[FN2_ID] = new FuncWrapper(_func2); m_funcs[FN3_ID] = new FuncWrapper(_func3); } ~DevManager_v3() { // } }; int _tmain(int argc, _TCHAR* argv[]) { DevManager_v3 dev_manager; dev_manager.initialize(); // -! . dev_manager.callMethod<int>(FN1_ID, 1); dev_manager.callMethod<int>(FN2_ID, 2, 3); dev_manager.callMethod<int>(FN3_ID, 4, 5); getchar(); }
initialize()
, which creates all the necessary devices for this system and places their methods in the collection. The caller does not even need to know the specific type of manager. The callMethod()
method callMethod()
does everything for us. For each specific system, the desired IDevManager
instance is IDevManager
using, say, <factory> . The caller needs only an ancestor IDevManager
.- It seems you have achieved your goal.
callMethod()
. If we pass a key that is not in the collection, we get an exception. Of course, you must first check whether the key is in the collection. But what to do when it turned out that the key does not exist (a method that does not exist is requested)? Generate an exception? And most importantly, at the compilation stage, we will not be able to catch this. This can happen when a device is missing in some system, or part of its methods.callMethod()
- it will not highlight the name / type / number of parameters. If we pass the wrong type of parameter, or the wrong number of parameters, an exception is waiting for us again, but in the call()
method of the test_impl
class. And again, we can't catch it at compile time. This can easily happen because of a programmer’s inattention.- “Sho, again ?!” ©
template <class ... T> class Dissembler : public T ... { };
- Holy simplicity. It will work for you until you have to inherit from two identical classes. Or in two classes there will be identical functions (diamond-shaped inheritance).
template <typename T, T t> struct ValueToType {}; template<typename C, class T> class ClassifiedWrapper {}; template<typename C, C c, class T> class ClassifiedWrapper<ValueToType<C, c>, T> : private T { public: ClassifiedWrapper(T&& fn) : T(std::forward<T>(fn)) {}; ClassifiedWrapper() = delete; virtual ~ClassifiedWrapper() {}; template <typename ... Args> std::result_of<T(Args...)>::type operator()(ValueToType<C, c>&&, Args&& ... args) { return T::operator()(std::forward<Args>(args) ...); }; };
ValueToType
class serves one purpose - to set the type depending on the value of the template parameter. The ClassifiedWrapper
class is another wrapper over a “callable object”. Its goal is to inherit from an object that has the operator operator()
defined, and delegate the call, but with an additional parameter that introduces “uniqueness”. I.e: class test { public: test() {} int fn1(int a) { cout << "test::fn1!!! " << a << endl; return ++a; } int fn2(int a, int b) { cout << "test::fn2!!! " << a << endl; return a + 2; } int fn3(int a, int b) { cout << "test::fn3!!! " << a << endl; return a + 3; } }; int _tmain(int argc, _TCHAR* argv[]) { test t; std::function<int(int, int)> _func2 = std::bind(&test::fn2, &t, std::placeholders::_1, std::placeholders::_2); ClassifiedWrapper<ValueToType<int, 0>, decltype(_func2)> cw1(std::ref(_func2)); ClassifiedWrapper<ValueToType<int, 1>, decltype(_func2)> cw2(std::ref(_func2)); cw1(ValueToType<int, 0>(), 2, 1); // cw2(ValueToType<int, 1>(), 3, 4); // cw1(ValueToType<int, 0>(), 1); // cw2(ValueToType<short, 1>(), 3, 4); // }
- And what to do with this good?
CalssifiedWrapper
. template <class ... T> class Dissembler {};
template <typename C, C c, class Func, class ... T> class Dissembler<ValueToType<C, c>, Func, T ...> : protected ClassifiedWrapper<ValueToType<C, c>, Func>, protected Dissembler<T ...> // , Dissembler<T ...> ValueToType<C, c> Func T ..., . , () { protected: using ClassifiedWrapper<ValueToType<C, c>, Func>::operator(); using Dissembler<T ...>::operator(); public: Dissembler(ValueToType<C, c>&& vt, Func&& func, T&& ... t) : ClassifiedWrapper<ValueToType<C, c>, Func>(std::forward<Func>(func)), Dissembler<T ...>(std::forward<T>(t) ...) {}; // Dissembler() = delete; virtual ~Dissembler() {}; // , . template <typename Cmd, Cmd cmd, typename ... Args> std::result_of<Func(Args...)>::type call(Args&& ... args) { // , ClassifiedWrapper'. // , return this->operator()(ValueToType<Cmd, cmd>(), std::forward<Args>(args)...); }; };
template <typename C, C c, class Func> class Dissembler<ValueToType<C, c>, Func> : protected ClassifiedWrapper<ValueToType<C, c>, Func> { public: Dissembler(ValueToType<C, c>&&, Func&& func) : ClassifiedWrapper<ValueToType<C, c>, Func>(std::forward<Func>(func)) {}; Dissembler() = delete; virtual ~Dissembler() {}; };
- You'll break your eyes. And on the fingers and clear understand you can?
ClassifiedWrapper
), which can, as it were, “attribute a unique label” (in fact, it does not attribute anything, I put it so beautifully) to any function.At the same time, he himself ClassifiedWrapper
is naturally unique (again, it is clear that with different template parameters). Next, we simply create a “static list” of such unique functions wrapped in ClassifiedWrapper
, and inherit from all of them. Fuh, easier, probably can not explain. In general, the focus I applied with the Variadic Template, many of which are described. In particular on Habré .-And why is there no method in the closure of recursion call
?
Dissembler
not for a variety of functions, but for one, then this idea does not make sense. Here's how to use all this household: int _tmain(int argc, _TCHAR* argv[]) { test t; std::function<int(int, int)> _func2 = std::bind(&test::fn2, &t, std::placeholders::_1, std::placeholders::_2); std::function<int(int, int)> _func3 = std::bind(&test::fn3, &t, std::placeholders::_1, std::placeholders::_2); Dissembler< ValueToType<int, 0>, decltype(_func2), ValueToType<char, 'a'>, decltype(_func3) > dis( ValueToType<int, 0>(), std::move(_func2), ValueToType<char, 'a'>(), std::move(_func3) ); dis.call<int, 0>(0, 1); dis.call<char, 'a'>(0, 1); getchar(); }
ValueToType<int, 0>
and ValueToType<char, 'a'>
. In the real problem, instead of the “incomprehensible” int and method numbers, it is much clearer to use enumerations with intelligible names of elements. Everything works quite clearly - we specify the call
type of the identifier and its value by the parameters of the template function , and we pass the parameters.-And how will yours look like in the end IDevManager
?
typedef Dissembler< ValueToType<int, 0>, std::function<int(int, int)>, ValueToType<int, 1>, std::function<int(int, int)> > SuperWrapper; class IDeviceManager { protected: std::unique_ptr<SuperWrapper> m_wrapperPtr; public: virtual ~IDeviceManager() {}; virtual void initialize() = 0; template <int command_id, typename ResType, typename ... Args> ResType callMethod(Args&& ... args) { return m_wrapperPtr->call<int, command_id>(std::forward<Args>(args)...); } }; class DevManager_v4 : public IDeviceManager { std::unique_ptr<test> m_test_ptr; public: void initialize() { // m_test_ptr.reset(new test); std::function<int(int, int)> _func2 = std::bind(&test::fn2, m_test_ptr.get(), std::placeholders::_1, std::placeholders::_2); std::function<int(int, int)> _func3 = std::bind(&test::fn3, m_test_ptr.get(), std::placeholders::_1, std::placeholders::_2); m_wrapperPtr.reset(new SuperWrapper( ValueToType<int, 0>(), std::move(_func2), ValueToType<int, 1>(), std::move(_func3) )); } }; int _tmain(int argc, _TCHAR* argv[]) { DevManager_v4 v4; v4.initialize(); v4.callMethod<1, int>(0, 1); v4.callMethod<0, int>(10, 31); getchar(); }
SuperWrapper
(for each system its own), you have to put in a separate header file. And to separate each definition #ifdef
"s, so that the" correct "is connected in the necessary project SuperWrapper
. It is for this reason that I put a question mark when writing option 4. Final?Source: https://habr.com/ru/post/231021/
All Articles