2009-05-18 14 views
14

मैं एक रैपर कैसे लिख सकता हूं जो किसी भी फ़ंक्शन को लपेट सकता है और इसे फ़ंक्शन की तरह ही कहा जा सकता है?सी ++: फ़ंक्शन रैपर जो फ़ंक्शन की तरह ही व्यवहार करता है

कारण मुझे इसकी आवश्यकता है: मुझे एक टाइमर ऑब्जेक्ट चाहिए जो फ़ंक्शन को लपेट सकता है और फ़ंक्शन की तरह ही व्यवहार कर सकता है, साथ ही यह सभी कॉलों के संचित समय को लॉग करता है।

परिदृश्य इस प्रकार दिखाई देगा:

// 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(); 
} 

(Btw, मैं gprof के बारे में पता है और क्रम रूपरेखा के लिए अन्य उपकरण है, लेकिन कुछ चयनित कार्यों का क्रम लॉग इन करने के इस तरह के एक Timer वस्तु होने और अधिक सुविधाजनक है । मेरी प्रयोजनों के लिए)

+0

क्या यह सी ++ होना चाहिए? यदि आपको "हाथों को गंदे होने" पर कोई फर्क नहीं पड़ता है, तो आप शायद सी के varargs का उपयोग करके कुछ हैक कर सकते हैं ... –

उत्तर

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

सामान्य रूप से महान विचार। लेकिन यह समय को मापने की अनुमति नहीं देता है, क्योंकि फंक्शन कॉल पूरा होने के बाद कोई भी कोड नहीं चलाया जा सकता है। –

+0

हां, आप केवल ऑपरेटर() में कोड चला सकते हैं। दुर्भाग्य से सी ++ को कार्यात्मक शैली का समर्थन करने के लिए डिज़ाइन नहीं किया गया था। * जादू * करने में सक्षम होने के लिए आपको प्रतिबंधों के आसपास काम करना होगा। लेकिन, हे, यह एक कारण है कि हम प्रोग्रामिंग क्यों पसंद करते हैं :) –

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; 
} 

नोट: यह सेकंड में समय को माप देगा। यदि आप उच्च परिशुद्धता टाइमर रखना चाहते हैं, तो आपको शायद ओएस विशिष्ट कार्यक्षमता (जैसे GetTickCount या QueryPerformanceCounter विंडोज़ पर जांचना होगा)।

यदि आप एक सामान्य फ़ंक्शन रैपर चाहते हैं, तो आपको Boost.Bind पर एक नज़र रखना चाहिए जो बहुत मददगार होगा।

+0

मैं रनटाइम को मापने के बारे में नहीं पूछ रहा था। मैं एक फ़ंक्शन ऑब्जेक्ट चाहता हूं जो किसी भी फ़ंक्शन को लपेट सके और एक ही ऑपरेटर() को फ़ंक्शन के समान हस्ताक्षर के साथ प्रदान कर सके। आपका ऑपरेटर() int लेने और शून्य वापस करने के लिए हार्ड कोड किया गया है। – Frank

+0

Boost.Bind के संदर्भ को जोड़ने के लिए धन्यवाद। इसके साथ यह संभव होना चाहिए ... – Frank

0

सी ++ कार्यों में प्रथम श्रेणी के नागरिक हैं, आप सचमुच एक कार्य के रूप में कार्य को पारित कर सकते हैं।

आप इसे एक पूर्णांक लेने के लिए और एक डबल वापस करना चाहते के बाद से:

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

नहीं, मुझे मूल्यों को गुजरने और वापस करने की परवाह है। ऑपरेटर() में मूल फ़ंक्शन के रूप में एक ही हस्ताक्षर होना चाहिए (int लें, इस विशिष्ट उदाहरण के मामले में दो बार वापस आएं।) – Frank

+0

मैंने int/double के लिए संपादित किया है। – Tom

1

आप एक बड़ी चुनौती के लिए बाहर हैं आप एक सामान्य वर्ग कि लपेट और एक मनमाना समारोह कॉल कर सकते हैं बनाने के लिए देख रहे हैं। इस मामले में आपको मज़ेदार (ऑपरेटर()) को दोबारा वापस करने और पैरामीटर के रूप में एक 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

10

असल में, आप वर्तमान सी ++ में क्या करना चाहते हैं असंभव है। समारोह आप रैप करने के लिए चाहते हैं के arity के किसी भी संख्या के लिए, आप द्वारा

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); 

या आप जा सकते हैं और दोनों के रूप में स्थिरांक और गैर स्थिरांक संस्करणों के लिए उन भार के जोड़ सकते हैं ऊपर समझाया। उचित मैक्रोज़ लिखने के लिए Boost.Preprocessor देखें।

आपको अवगत होना चाहिए कि अगर आप मनमानी कॉलबेल (न केवल कार्य) पारित करने में सक्षम होना चाहते हैं तो पूरी चीज अधिक कठिन हो जाएगी, क्योंकि आपको अपना परिणाम प्रकार प्राप्त करने के लिए एक रास्ता चाहिए (यह इतना आसान नहीं है)। सी ++ 1 एक्स इस तरह के सामान को आसान बना देगा।

+0

ग्रेट! मैंने बस enable_if सामान को याद रखने की कोशिश की और मैक्रोज़ के बिना इसे लागू करने जा रहा था (जिसे मुझे पता नहीं था)। आपने मेरा समय बचाया धन्यवाद। –

+0

मुझे खुशी है कि आपको यह पसंद है :) –

+0

जैसा कि आपने संकेत दिया है, सी ++ 0x (या 1x मुझे लगता है) के साथ सही अग्रेषण वास्तव में आसान हो जाता है। – Eclipse

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); 
+0

धन्यवाद, लेकिन एक समस्या है: यह सामान्य नहीं है। आपने इसे एक ऐसे फ़ंक्शन के साथ काम करने के लिए कड़ी मेहनत की है जो int लेता है और दोहरा देता है। लेकिन टाइमर सामान्य होना चाहिए और किसी भी समारोह के साथ काम करना चाहिए। – Frank

1

अपने संकलक variadic मैक्रो का समर्थन करता है, तो मैं यह कोशिश करेंगे:

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

मुझे लगता है कि आपको परीक्षण उद्देश्य के लिए इसकी आवश्यकता है और वे वास्तविक प्रॉक्सी या सजावटी के रूप में उनका उपयोग नहीं करेंगे। तो आपको ऑपरेटर() का उपयोग करने की आवश्यकता नहीं होगी और कॉल की किसी अन्य कम-कम सुविधाजनक विधि का उपयोग कर सकते हैं।

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; 
} 
+1

+1, क्योंकि यह एक अच्छा दृष्टिकोण imho है। बस कुछ पता होना चाहिए: पूर्ण अभिव्यक्ति के अंत में टाइमर ऑब्जेक्ट नष्ट हो जाता है। तो यदि आप f करते हैं (test_time (test1, elapsedTime) .call (20)); उस समय में "एफ" में से एक भी शामिल होगा। बस तुम इतना जानते हो।चूंकि यह केवल परीक्षण उद्देश्यों के लिए है, इसलिए इससे कोई फर्क नहीं पड़ता क्योंकि कोई इससे बच सकता है। –

+1

यदि आप चाहें तो आप भी सरोगेट फ़ंक्शंस का उपयोग कर सकते हैं। फिर यह इस तरह दिखेगा: test_time (test2, elapsedTime) (20); इसके लिए, टाइमरवापर को इसकी आवश्यकता है: ऑपरेटर TFunction() {वापसी कॉल; } अब, यदि आप "(20)" लिखते हैं, तो कंपाइलर टाइमर को फ़ंक्शन-पॉइंटर में परिवर्तित करेगा और इसे तर्क के साथ कॉल करेगा। मेरे जवाब में कोई बुरा ओप() अधिभार नहीं है :) –

+0

+1। मुझे लगता है कि फ़ंक्शन ऑब्जेक्ट सदस्य को सीधे उजागर करके आप पूरी अग्रेषण समस्या के आसपास कैसे पहुंचते हैं! मैंने कभी इसके बारे में सोचा नहीं होगा। –

2

Stroustrup operator-> अधिक भार के साथ एक समारोह आवरण (injaction) कौशल का प्रदर्शन किया था नहीं इस्तेमाल करना चाहिए। मुख्य विचार यह है: operator-> दोहराए जाने तक दोबारा कॉल किया जाएगा जब तक कि यह मूल सूचक प्रकार से मिलता है, तो Timer::operator-> एक अस्थायी वस्तु लौटाएं, और अस्थायी वस्तु इसके सूचक को वापस कर दें। तो फिर क्या होगा निम्नलिखित:

  1. अस्थायी obj बनाया (ctor कहा जाता है)।
  2. लक्षित फ़ंक्शन कहा जाता है।
  3. अस्थायी विलुप्त obj (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

+1, हाँ यह एक अच्छा तरीका है। लेकिन चूंकि मायकोला गोलबुएव के उत्तर पर लिटब बताते हैं, अस्थायी को पूर्ण अभिव्यक्ति के अंत में हटा दिया जाएगा, जो कॉल के तुरंत बाद से भिन्न हो सकता है - उदा। "big_slow_function (टाइमर-> एफ (1, 2))" वास्तव में big_slow_function() को चलाने के लिए समय भी शामिल करेगा। यह एक अप्रिय गलती नहीं है, केवल कुछ पता होना चाहिए। –

+2

संदर्भ के लिए, स्ट्रॉस्ट्रप का मूल विवरण [यहां पाया जा सकता है] (http://www.stroustrup.com/wrapper.pdf) –

1

यदि आप शामिल हैं std :: tr1 :: फ़ंक्शन के कार्यान्वयन को देखते हैं तो आपको शायद एक उत्तर मिल सकता है।

सी ++ 11 में, std :: function variadic टेम्पलेट्स के साथ कार्यान्वित किया गया है। इस तरह के टेम्पलेट का उपयोग करना अपने टाइमर वर्ग मैक्रो और टेम्पलेट का उपयोग कर की तरह

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 आश्वस्त करने के लिए() पहले कहा जाता है। एफ का परिणाम एंड्रैप() को पास किया जाता है जो इसे वापस देता है। तो उत्पादन होता है:

beginWrap() 
foo 10.100000 
endWrap() 

और परिणाम आर 10.1 शामिल हैं।

संबंधित मुद्दे