2009-05-18 30 views
14

如何編寫一個可以包裝任何函數並可以像函數本身一樣調用的包裝器?C++:函數包裝器,其功能本身就像函數本身

我需要這個的原因:我想要一個Timer對象,它可以包裝一個函數並像函數本身一樣運行,並記錄所有調用的累計時間。

的情況是這樣的:

// a function whose runtime should be logged 
double foo(int x) { 
    // do something that takes some time ... 
} 

Timer timed_foo(&foo); // timed_foo is a wrapping fct obj 
double a = timed_foo(3); 
double b = timed_foo(2); 
double c = timed_foo(5); 
std::cout << "Elapsed: " << timed_foo.GetElapsedTime(); 

我怎麼可以這樣寫Timer類?

我想是這樣的:

#include <tr1/functional> 
using std::tr1::function; 

template<class Function> 
class Timer { 

public: 

    Timer(Function& fct) 
    : fct_(fct) {} 

    ??? operator()(???){ 
    // call the fct_, 
    // measure runtime and add to elapsed_time_ 
    } 

    long GetElapsedTime() { return elapsed_time_; } 

private: 
    Function& fct_; 
    long elapsed_time_; 
}; 

int main(int argc, char** argv){ 
    typedef function<double(int)> MyFct; 
    MyFct fct = &foo; 
    Timer<MyFct> timed_foo(fct); 
    double a = timed_foo(3); 
    double b = timed_foo(2); 
    double c = timed_foo(5); 
    std::cout << "Elapsed: " << timed_foo.GetElapsedTime(); 
} 

(順便說一句,我知道的gprof等工具進行剖析運行,但有這樣一個Timer對象登錄的一些特定功能,運行時更加方便。我的目的)

+0

它必須是C++嗎?如果你不介意「讓你的手變髒」,你可能可以使用C的可變參數來攻擊一些東西... – 2009-05-19 02:00:35

回答

8

這裏是包裝功能的容易方式。

template<typename T> 
class Functor { 
    T f; 
public: 
    Functor(T t){ 
     f = t; 
    } 
    T& operator()(){ 
    return f; 
    } 
}; 


int add(int a, int b) 
{ 
    return a+b; 
} 

void testing() 
{ 
    Functor<int (*)(int, int)> f(add); 
    cout << f()(2,3); 
} 
+1

一般的好主意。但是它不允許測量時間,因爲在函數調用完成後沒有代碼可以運行。 – 2009-05-18 21:32:28

+0

是的,您只能在運算符()中運行代碼。不幸的是,C++沒有被設計爲支持功能風格。爲了能夠做到*魔法*你必須解決限制。但是,嘿,這就是我們喜歡編程的原因之一:) – 2009-05-19 04:55:59

1

這不是真的明白我要你在找什麼。然而,對於給定的例子,它只是:

void operator() (int x) 
{ 
    clock_t start_time = ::clock(); // time before calling 
    fct_(x);       // call function 
    clock_t end_time = ::clock();  // time when done 

    elapsed_time_ += (end_time - start_time)/CLOCKS_PER_SEC; 
} 

注意:這將以秒爲單位來測量時間。如果您想要使用高精度計時器,則可能需要檢查操作系統特定的功能(如Windows上的GetTickCountQueryPerformanceCounter)。

如果你想有一個通用函數包裝,你應該看看Boost.Bind,這將有助於trevenudously。

+0

我並不是在問如何測量運行時間。我想要一個可以包裝任何函數的函數對象,並提供一個與函數本身具有相同簽名的運算符()。你的operator()被硬編碼爲int並返回void。 – Frank 2009-05-18 20:01:10

+0

感謝您添加對Boost.Bind的引用。因此,應該有可能... – Frank 2009-05-18 20:09:42

0

在C++函數是第一類公民,你可以從字面上傳遞一個函數作爲一個值。

既然你想讓它需要一個int和返回雙:

Timer(double (*pt2Function)(int input)) {... 
+0

不,我關心傳遞和返回值。運算符()應該與原始函數具有相同的簽名(以int爲例,在此特定示例中返回double)。 – Frank 2009-05-18 20:02:06

+0

我編輯了int/double。 – Tom 2009-05-18 23:10:46

1

你出來一個很大的挑戰,如果你正在尋找創建一個通用類,可以包裝,並調用任意函數。在這種情況下,你必須讓functor(operator())返回double並將int作爲參數。然後,您創建了一系列可以調用具有相同簽名的所有函數的類。只要你想添加更多類型的函數,你就需要更多的簽名函數,例如

MyClass goo(double a, double b) 
{ 
    // .. 
} 

template<class Function> 
class Timer { 

public: 

    Timer(Function& fct) 
    : fct_(fct) {} 

    MyClass operator()(double a, double b){ 

    } 

}; 

編輯:一些拼寫錯誤

+0

請注意,只有在對包裝函數的實際結果感興趣時才需要運算符的返回值,即上述運算符也可能是無效的。 – ralphtheninja 2009-05-18 20:16:51

10

基本上,你想做的事情在當前的C++中是不可能的。對於任意數量的要包裝的功能參數數量,則需要通過

const reference 
non-const reference 

超負荷但後來它仍然不是完美轉發(一些邊緣的情況下仍然站立),但它應該工作以及合理。如果你限制自己的常量引用,你可以用這一個(未測試)去:

template<class Function> 
class Timer { 
    typedef typename boost::function_types 
     ::result_type<Function>::type return_type; 

public: 

    Timer(Function fct) 
    : fct_(fct) {} 

// macro generating one overload 
#define FN(Z, N, D) \ 
    BOOST_PP_EXPR_IF(N, template<BOOST_PP_ENUM_PARAMS(N, typename T)>) \ 
    return_type operator()(BOOST_PP_ENUM_BINARY_PARAMS(N, T, const& t)) { \ 
     /* some stuff here */ \ 
     fct_(ENUM_PARAMS(N, t)); \ 
    } 

// generate overloads for up to 10 parameters 
BOOST_PP_REPEAT(10, FN, ~) 
#undef FN 

    long GetElapsedTime() { return elapsed_time_; } 

private: 
    // void() -> void(*)() 
    typename boost::decay<Function>::type fct_; 
    long elapsed_time_; 
}; 

注意,返回類型,你可以使用升壓轉換器的功能類型庫。然後

Timer<void(int)> t(&foo); 
t(10); 

您也可以使用純值參數超載,然後如果你想通過引用傳遞的東西,利用boost::ref。這實際上是一個很常見的技術,尤其是當這些參數將被保存(這種技術也可用於boost::bind):

// if you want to have reference parameters: 
void bar(int &i) { i = 10; } 

Timer<void(int&)> f(&bar); 
int a; 
f(boost::ref(a)); 
assert(a == 10); 

或者你可以去添加這些重載const和非const版本作爲如上所述。查看Boost.Preprocessor瞭解如何編寫正確的宏。

你應該意識到,如果你想能夠傳遞任意可調參數(不僅是函數),整個事情會變得更加困難,因爲你需要一種方法來獲得它們的結果類型(這並不容易)。 C++ 1x將使這種東西更容易。

+0

太棒了!我只是想記住enable_if的東西,並將實現這個沒有宏(我不知道)。你節省了我的時間。謝謝。 – 2009-05-18 20:41:42

0

這是我怎麼會做它,使用,而不是一個模板函數指針:

// pointer to a function of the form: double foo(int x); 
typedef double (*MyFunc) (int); 


// your function 
double foo (int x) { 
    // do something 
    return 1.5 * x; 
} 


class Timer { 
public: 

    Timer (MyFunc ptr) 
    : m_ptr (ptr) 
    { } 

    double operator() (int x) { 
    return m_ptr (x); 
    } 

private: 
    MyFunc m_ptr; 
}; 

我改成了沒有取到函數的引用,但只是一個普通的函數指針。用法是一樣的:

Timer t(&foo); 
    // call function directly 
    foo(i); 
    // call it through the wrapper 
    t(i); 
1

如果你的編譯器支持複雜的宏,我想試試這個:

class Timer { 
    Timer();// when created notes start time 
    ~ Timer();// when destroyed notes end time, computes elapsed time 
} 

#define TIME_MACRO(fn, ...) { Timer t; fn(_VA_ARGS_); } 

因此,要使用它,你可以這樣做:

void test_me(int a, float b); 

TIME_MACRO(test_me(a,b)); 

這是關閉的,你需要四處尋找返回類型的工作(我想你必須添加一個類型名稱到TIME_MACRO調用,然後讓它生成一個臨時變量)。

5

我假設你需要這個測試目的,並不打算將它們用作真正的代理或裝飾器。所以你不需要使用operator(),並且可以使用任何其他不太方便的調用方法。

template <typename TFunction> 
class TimerWrapper 
{ 
public: 
    TimerWrapper(TFunction function, clock_t& elapsedTime): 
     call(function), 
     startTime_(::clock()), 
     elapsedTime_(elapsedTime) 
    { 
    } 

    ~TimerWrapper() 
    { 
     const clock_t endTime_ = ::clock(); 
     const clock_t diff = (endTime_ - startTime_); 
     elapsedTime_ += diff; 
    } 

    TFunction call; 
private: 
    const clock_t startTime_; 
    clock_t& elapsedTime_; 
}; 


template <typename TFunction> 
TimerWrapper<TFunction> test_time(TFunction function, clock_t& elapsedTime) 
{ 
    return TimerWrapper<TFunction>(function, elapsedTime); 
} 

所以測試一些你的工作,你應該只使用test_time功能,而不是直接TimerWrapper結構

int test1() 
{ 
    std::cout << "test1\n"; 
    return 0; 
} 

void test2(int parameter) 
{ 
    std::cout << "test2 with parameter " << parameter << "\n"; 
} 

int main() 
{ 
    clock_t elapsedTime = 0; 
    test_time(test1, elapsedTime).call(); 
    test_time(test2, elapsedTime).call(20); 
    double result = test_time(sqrt, elapsedTime).call(9.0); 

    std::cout << "result = " << result << std::endl; 
    std::cout << elapsedTime << std::endl; 

    return 0; 
} 
2

斯特勞斯已經證明一個函數包裝(injaction)技能與超載operator->。關鍵的想法是:operator->將重複調用,直到它遇到一個本機指針類型,所以讓Timer::operator->返回一個臨時對象,並且臨時對象返回它的指針。然後會發生以下情況:

  1. temp obj created(ctor called)。
  2. 調用目標函數。
  3. temp obj destructed(dtor called)。

而且你可以在ctor和dtor內注入任何代碼。喜歡這個。

template < class F > 
class Holder { 
public: 
    Holder (F v) : f(v) { std::cout << "Start!" << std::endl ; } 
    ~Holder()   { std::cout << "Stop!" << std::endl ; } 
    Holder* operator->() { return this ; } 
    F f ; 
} ; 

template < class F > 
class Timer { 
public: 
    Timer (F v) : f(v) {} 
    Holder<F> operator->() { Holder<F> h(f) ; return h ; } 
    F f ; 
} ; 

int foo (int a, int b) { std::cout << "foo()" << std::endl ; } 

int main() 
{ 
    Timer<int(*)(int,int)> timer(foo) ; 
    timer->f(1,2) ; 
} 

實施和使用都很容易。

1

如果您查看包含的std :: tr1 :: function的實現,您可能會找到答案。

在C++ 11中,std :: function是使用可變參數模板實現的。使用這樣的模板您的Timer類可以像

template<typename> 
class Timer; 

template<typename R, typename... T> 
class Timer<R(T...)> 
{ 
    typedef R (*function_type)(T...); 

    function_type function; 
public: 
    Timer(function_type f) 
    { 
     function = f; 
    } 

    R operator() (T&&... a) 
    { 
     // timer starts here 
     R r = function(std::forward<T>(a)...); 
     // timer ends here 
     return r; 
    } 
}; 

float some_function(int x, double y) 
{ 
    return static_cast<float>(static_cast<double>(x) * y); 
} 


Timer<float(int,double)> timed_function(some_function); // create a timed function 

float r = timed_function(3,6.0); // call the timed function 
1

一個解決方案使用宏和模板:例如,你想包裝

double foo(double i) { printf("foo %f\n",i); return i; } 
double r = WRAP(foo(10.1)); 

之前和調用foo()的包裝函數beginWrap(後)和endWrap()應該被調用。 (配endWrap()是一個模板函數。)

void beginWrap() { printf("beginWrap()\n"); } 
template <class T> T endWrap(const T& t) { printf("endWrap()\n"); return t; } 

#define WRAP(f) endWrap((beginWrap(), f)); 

使用逗號操作者的優先級,以確保beginWrap()首先被調用。 f的結果被傳遞給endWrap(),它只是返回它。 所以輸出是:

beginWrap() 
foo 10.100000 
endWrap() 

而結果r包含10.1。