Если вы знаете, какую функцию вы собираетесь звонить, вы можете сделать что-то вроде следующего. Это даже позволяет специфицировать возвращаемое значение по умолчанию для функции в случае exec==false
. Я уверен, что не рассматривал все возможные ловушки опорных аргументов возврата, константные функции-члены и т. Д. Но я уверен, что вы можете приспособить его, если хотите его использовать.
#include <iostream>
struct X {
double callX(const int& x){ return x/100.;};
};
struct Y {
int callY(const std::string& y){ return y.length();};
};
template<typename F> class Monitor;
template<typename T, typename Ret, typename ...Args>
class Monitor<Ret(T::*)(Args...)> {
T& ref;
Ret(T::*func)(Args...);
Ret defaultRet;
bool exec;
public:
Monitor(T& ref, Ret(T::*func)(Args...), Ret defaultRet = Ret())
: ref(ref),
func(func),
defaultRet(defaultRet),
exec(true){};
void setExec(bool e) {exec = e;};
Ret call(Args&&... args) {
if(exec)
return (ref.*func)(std::forward<Args>(args)...);
else
return defaultRet;
};
};
template<typename T, typename Ret, typename ...Args>
auto makeMonitor(T& x, Ret(T::*f)(Args...), Ret r = Ret()) {
return Monitor<Ret(T::*)(Args...)>(x,f,r);
}
int main() {
X x;
Y y;
auto xmon = makeMonitor(x, &X::callX);
auto ymon = makeMonitor(y, &Y::callY);
auto ymon_def = makeMonitor(y, &Y::callY, 123);
std::cout << "callX(3)=" << xmon.call(3) << std::endl;
std::cout << "callY(\"hello\")=" << ymon.call("hello") << std::endl;
std::cout << "[default return] callY(\"hello\")=" << ymon_def.call("hello") << std::endl;
xmon.setExec(false);
ymon.setExec(false);
ymon_def.setExec(false);
std::cout << "After setExec(false):" << std::endl;
std::cout << "callX(3)=" << xmon.call(3) << std::endl;
std::cout << "callY(\"hello\")=" << ymon.call("hello") << std::endl;
std::cout << "[default return] callY(\"hello\")=" << ymon_def.call("hello") << std::endl;
return 0;
}
Выход:
callX(3)=0.03
callY("hello")=5
[default return] callY("hello")=5
After setExec(false):
callX(3)=0
callY("hello")=0
[default return] callY("hello")=123
Рабочий пример является here.
«Очевидное» решение, о котором вы упомянули, может быть упрощено, поэтому вам нужно только определить один дополнительный (макет) класс и никаких дополнительных базовых классов. Если вы не возражаете, небольшие потери производительности из-за функций виртуальных членов, вы можете это сделать так:
#include <iostream>
struct MockX;
struct X {
typedef MockX mock;
virtual double doX(int x){ return x/100.;};
};
struct MockX : X {
virtual double doX(int x){ return 0.;};
};
struct MockY;
struct Y {
typedef MockY mock;
virtual int doY(std::string y){ return y.length();};
};
struct MockY : Y {
virtual int doY(std::string y){ return 123;};
};
template <typename T>
struct Monitor {
T& ref;
static typename T::mock dummy;
bool exec;
Monitor(T& ref) : ref(ref), exec(true){};
void setExec(bool e){exec = e;};
T* operator->(){
if(exec)
return &ref;
else
return &dummy;
};
};
template<typename T>
typename T::mock Monitor<T>::dummy{};
int main() {
X x;
Y y;
auto xmon = Monitor<X>(x);
auto ymon = Monitor<Y>(y);
std::cout << "doX(3)=" << xmon->doX(3) << std::endl;
std::cout << "doY(\"hello\")=" << ymon->doY("hello") << std::endl;
xmon.setExec(false);
ymon.setExec(false);
std::cout << "After setExec(false):" << std::endl;
std::cout << "doX(3)=" << xmon->doX(3) << std::endl;
std::cout << "doY(\"hello\")=" << ymon->doY("hello") << std::endl;
return 0;
}
Я сделал dummy
фиктивный объект static
, так что будет только один экземпляр для каждого типа вы мониторинг. Все, что вам нужно, это typedef в реальном классе, определяющий ваш mock-класс, и класс mock, наследующий от реального класса, и переопределение (виртуальных) методов, которые вы хотите отключить, когда exec==false
. Вы должны знать, что даже методы, которые вы не переопределяете, будут вызываться на объекте dummy
при exec==false
, чтобы они не могли вести себя так, как ожидалось.
Однако, это также может быть преимущество: Если вы пишете X
и Y
таким образом, что по умолчанию построенного объект а (или один построен со специальным флагом, указанным в конструкторе) ведет себя как макет класс, вы дон 't даже нужен макет-класс (просто постройте dummy
таким образом). Но тогда вы могли бы почти построить эту «отключенную» функциональность в X
и вам не нужен монитор ... ;-)
Любая критика о том, как улучшить мой вопрос или уточнить? – MatthiasB
Я думаю, что если вы хотите иметь возможность вызывать любую функцию-член наблюдаемого класса как 'monitor-> somefunc', нет другого способа, кроме класса mock. Вы возвращаете 'T *' из оператора '->', поэтому, что бы это ни было, он должен предоставить 'somefunc', иначе он даже не будет компилироваться. Если вы хотите только вызвать метод * specific * для контролируемого класса, вы можете вернуть объект функции, вызывающий этот метод (или пустой фиктивный элемент, если флаг является ложным) вместо самого класса (возможно, не используя '->', хотя). – Oguk