2014-10-13 2 views
-1

Предположим, что у вас есть класс Foo с функцией Foo::bar(). Окружающая эта функция - класс Monitor<Foo>, который обертывается вокруг Foo и пересылает любой вызов функции путем перегрузки operator->.Выполнение функции условного члена

Кроме того, класс Monitor имеет булевский флаг execute. Если значение execute истинно, все вызовы функций из Foo должны выполняться нормально, но если для execute установлено значение false, выполнение должно быть пропущено.

Следующий фрагмент кода показывает, как это может выглядеть следующим образом:

#include <iostream> 

using namespace std; 

class Foo { 
    void bar() {std::cout << "Foo::bar()";} 
}; 

template<typename T> class Monitor<T> { 
    T& ref; 
    bool exec; 
public: 
    Monitor(T& obj) : ref(obj), exec(true) {} 
    T* operator->() {/* if exec */ return &ref;} 
    void setExec(bool e) {exec = e;} 
}; 

int main() { 
    Foo foo; 
    Monitor<Foo> monitor(foo); 

    monitor->bar(); // call Foo::bar(); 
    monitor.setExec(false); 
    monitor->bar(); // do nothing 
} 

возможно ли это реализовать? Очевидным решением является наличие базового класса IFoo и a Реализация макета MockFoo ничего не делает, а затем возвращает указатель на объект MockFoo , когда вызывается operator->. Это делает все это довольно негибким, однако, поскольку вы должны предоставить объект Mock для любого класса, который вы хотите контролировать.

Итак, есть ли лучший способ достичь этого?

+0

Любая критика о том, как улучшить мой вопрос или уточнить? – MatthiasB

+0

Я думаю, что если вы хотите иметь возможность вызывать любую функцию-член наблюдаемого класса как 'monitor-> somefunc', нет другого способа, кроме класса mock. Вы возвращаете 'T *' из оператора '->', поэтому, что бы это ни было, он должен предоставить 'somefunc', иначе он даже не будет компилироваться. Если вы хотите только вызвать метод * specific * для контролируемого класса, вы можете вернуть объект функции, вызывающий этот метод (или пустой фиктивный элемент, если флаг является ложным) вместо самого класса (возможно, не используя '->', хотя). – Oguk

ответ

1

Если вы знаете, какую функцию вы собираетесь звонить, вы можете сделать что-то вроде следующего. Это даже позволяет специфицировать возвращаемое значение по умолчанию для функции в случае 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 и вам не нужен монитор ... ;-)

+0

Ваша идея показывает мне, насколько сложно это то, что я хочу (особенно если вам приходится иметь дело с типами возврата ...). Моя цель состояла в том, чтобы обеспечить прозрачность интерфейса, поэтому нет никакой разницы в вызове 'pX-> callX()' или 'monitorX-> callX()'. – MatthiasB

+0

Это действительно невозможно, я думаю (потому что вы не можете программно изменить класс). Вероятно, ваше «очевидное решение» - лучшее из них ... Я попытаюсь придумать, как сделать этот подход более удобным. – Oguk

+0

В конце я использовал макетный подход. Поскольку у меня уже есть структура класса с базовым классом, это не слишком много изменений. Жаль, что нет другого пути, хотя ... – MatthiasB

Смежные вопросы