2009-05-18 13 views
14

Wie kann ich einen Wrapper schreiben, der jede Funktion umbrechen kann und genau wie die Funktion selbst aufgerufen werden kann?C++: Funktionswrapper, der sich genau wie die Funktion selbst verhält

Der Grund, warum ich das brauche: Ich möchte ein Timer-Objekt, das eine Funktion umhüllen und sich genau wie die Funktion selbst verhalten kann, und es protokolliert die akkumulierte Zeit aller seiner Aufrufe.

würde das Szenario wie folgt aussehen:

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

Wie kann ich diese Timer Klasse schreiben?

Ich versuche, so etwas wie dieses:

#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, ich kenne gprof und andere Werkzeuge zur Laufzeitprofilierung, sondern eine solche mit Timer Objekt die Laufzeit von wenigen ausgewählten Funktionen zu protokollieren ist bequemer . für meine Zwecke)

+0

Muss es C++ sein? Wenn es dir nichts ausmacht, "deine Hände schmutzig zu machen", kannst du wahrscheinlich etwas mit Cs varargs hacken ... –

Antwort

8

Hier ist ein leicht Weise Funktionen zu wickeln.

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

Großartige Idee im Allgemeinen. Es erlaubt jedoch keine Zeitmessung, da nach dem Funktionsaufruf kein Code ausgeführt werden kann. –

+0

Ja, Sie können Code nur in operator() ausführen. Leider wurde C++ nicht zur Unterstützung des funktionalen Stils entwickelt. Um * Magie * zu können, musst du die Einschränkungen umgehen. Aber, hey, das ist einer der Gründe, warum wir gerne programmieren :) –

1

es ist nicht wirklich klar, mich für das, was Sie .. Allerdings suchen, für das gegebene Beispiel, dann ist es einfach:

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

Hinweis: Damit wird die Zeit in Sekunden gemessen. Wenn Sie hochpräzise Timer benötigen, müssen Sie wahrscheinlich die betriebssystemspezifische Funktionalität überprüfen (z. B. GetTickCount oder QueryPerformanceCounter unter Windows).

Wenn Sie einen generischen Funktionswrapper haben wollen, sollten Sie einen Blick auf Boost.Bind werfen, der sehr hilfreich sein wird.

+0

Ich wollte nicht wissen, wie man die Laufzeit misst. Ich möchte ein Funktionsobjekt, das jede Funktion umbrechen kann und einen Operator() mit der gleichen Signatur wie die Funktion selbst versehen kann. Ihr Operator() ist fest codiert, um int zu übernehmen und void zurückzugeben. – Frank

+0

Vielen Dank, dass Sie den Verweis auf Boost.Bind hinzugefügt haben. Damit sollte es möglich sein ... – Frank

0

In C++ Funktionen sind Bürger der ersten Klasse, können Sie buchstäblich eine Funktion als Wert übergeben.

Da Sie wollen, dass es einen int nehmen und eine doppelte Rückkehr:

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

Nein, mir ist es wichtig Werte zu übergeben und zurück zu geben. Der Operator() sollte die gleiche Signatur wie die ursprüngliche Funktion haben (take int, return double im Fall dieses spezifischen Beispiels). – Frank

+0

Ich habe für das int/double bearbeitet. – Tom

1

Sie sind für eine große Herausforderung, wenn Sie eine generische Klasse zu schaffen suchen, und rufen Sie eine beliebige Funktion wickeln kann. In diesem Fall müssten Sie den Funktor (den Operator()) dazu veranlassen, double zurückzugeben und einen int als Parameter zu verwenden. Dann haben Sie eine Familie von Klassen erstellt, die alle Funktionen mit derselben Signatur aufrufen können. Sobald Sie weitere Arten von Funktionen hinzufügen möchten, benötigen Sie mehr Funktoren dieser Signatur, z.

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

template<class Function> 
class Timer { 

public: 

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

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

    } 

}; 

EDIT: Einige Rechtschreibfehler

+0

Beachten Sie, dass der Rückgabewert des Operators nur benötigt wird, wenn Sie an dem tatsächlichen Ergebnis der umbrochenen Funktion interessiert sind, d. H. Der obige Operator könnte genauso gut eine Lücke sein. – ralphtheninja

10

Grundsätzlich, was Sie tun möchten, ist in C++ derzeit unmöglich. Für eine beliebige Anzahl von arity der Funktion, die Sie wickeln möchten, müssen Sie durch

Überlastung
const reference 
non-const reference 

Aber dann noch, es ist nicht perfekt Weiterleitung (einige Grenzfälle noch stehen), aber es sollte auch vernünftig arbeiten.Wenn Sie sich auf const Referenzen begrenzen, können Sie mit diesem gehen (nicht getestet):

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

Beachten Sie, dass für den Rückgabetyp, können Sie boost die Funktionstypen Bibliothek verwenden. Dann

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

Sie können auch reine Wertparameter Überlastung verwenden, und dann, wenn Sie etwas durch Verweis übergeben möchten, verwenden Sie boost::ref. Das ist eigentlich eine ziemlich verbreitete Technik, vor allem, wenn solche Parameter gespeichert werden werden (diese Technik auch für boost::bind verwendet wird):

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

Oder Sie können diese Überlastungen sowohl const und nicht-const Versionen gehen und fügen Sie als oben erklärt. Sehen Sie in Boost.Preprocessor nach, wie Sie die richtigen Makros schreiben.

Sie sollten sich bewusst sein, dass das Ganze schwieriger wird, wenn Sie beliebige Callables (nicht nur Funktionen) übergeben wollen, da Sie einen Weg brauchen, um ihren Ergebnistyp zu erhalten (das ist gar nicht so einfach)). C++ 1x wird diese Art von Dingen einfacher machen.

+0

Großartig! Ich habe gerade versucht, sich an enable_if Zeug zu erinnern und würde dies ohne Makros implementieren (was mir nicht bewusst war). Du hast meine Zeit gerettet. Vielen Dank. –

+0

Ich bin froh, dass Sie es mögen :) –

+0

Wie Sie angedeutet, mit C++ 0x (oder 1x ich denke) perfekte Weiterleitung wird wirklich einfach. – Eclipse

0

Hier ist, wie ich es tun würde, einen Funktionszeiger anstelle einer Vorlage:

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

Habe ich es nicht einen Verweis auf die Funktion übernehmen, sondern nur einen einfacher Funktionszeiger. Verbrauch bleibt gleich:

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

Danke, aber es gibt ein Problem: Es ist nicht generisch. Sie haben es so programmiert, dass es mit einer Funktion arbeitet, die int übernimmt und double zurückgibt. Aber der Timer sollte generisch sein und mit jeder Funktion funktionieren. – Frank

1

Wenn Ihr Compiler variadische Makros unterstützt, würde ich versuchen Sie dies:

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

Also, es zu benutzen, würden Sie dies tun:

void test_me(int a, float b); 

TIME_MACRO(test_me(a,b)); 

Das ist aus dem Stegreif, und Sie müssten herumspielen, um Rückgabetypen zum Arbeiten zu bringen (ich denke, dass Sie dem TIME_MACRO-Aufruf einen Typnamen hinzufügen und dann eine temporäre Variable generieren lassen müssen).

5

Ich nehme an, dass Sie dies für Testzwecke brauchen und sie nicht als echte Proxies oder Dekorateure verwenden werden. Sie müssen also nicht operator() verwenden und können eine andere, weniger bequeme Methode verwenden.

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

So funktionieren einige von Ihnen testen Sie nur test_time Funktion und nicht die direkte TimerWrapper Struktur

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, da dies ein guter Ansatz ist, imho. Nur etwas, auf das Sie achten sollten: Das Timer-Objekt wird am Ende des vollständigen Ausdrucks zerstört. Also, wenn Sie f (test_time (test1, elapsedTime) .call (20)); Die Zeit wird auch die von "f" beinhalten. Nur damit du es weißt.Da es nur für Testzwecke ist, ist es wahrscheinlich nicht wichtig, da man es vermeiden kann. –

+1

Sie können auch Ersatzfunktionen verwenden, wenn Sie möchten. Dann sieht es so aus: test_time (test2, elapsedTime) (20); Dafür benötigt TimerWrapper diesen: operator TFunction() {return call; } Wenn Sie nun "(20)" schreiben, konvertiert der Compiler den Timer in den Funktionszeiger und ruft ihn mit den Argumenten auf. Keine böse op() Überladung wie in meiner Antwort dann :) –

+0

+1. Ich mag, wie Sie das ganze Weiterleitungsproblem umgehen, indem Sie einfach das Funktionsobjektglied direkt aussetzen! Daran hätte ich nie gedacht. –

2

Stroustrup mit Überlastung verwenden sollte die operator-> eine Funktion Wrapper (injaction) Fähigkeit bewiesen hatte. Die Schlüsselidee ist: operator-> wird wiederholt aufgerufen, bis es einen systemeigenen Zeigertyp trifft, also lassen Sie Timer::operator-> ein temporäres Objekt zurückgeben, und das temporäre Objekt gibt seinen Zeiger zurück. Dann wird folgendes passieren:

  1. temp obj erstellt (ctor genannt).
  2. Zielfunktion aufgerufen.
  3. temp obj zerstört (dtor genannt).

Und Sie können jeden Code innerhalb der CTor und der dtor injizieren. So was.

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

Die Implementierung und die Verwendung sind beide einfach.

+1

+1, ja das ist ein guter Ansatz. Aber wie Litb auf Mykola Golubyevs Antwort hinweist, wird das Temporäre am Ende des beinhaltenden Volltexts gelöscht, der anders als unmittelbar nach dem Ruf nach f sein könnte - z. "big_slow_function (timer-> f (1, 2))" enthält auch die Zeit für die Ausführung von big_slow_function(). Das ist keine lähmende Schuld, nur etwas, dessen man sich bewusst sein muss. –

+2

Zur Referenz, Stroustrups ursprüngliche Beschreibung [finden Sie hier] (http://www.stroustrup.com/wrapper.pdf) –

1

Sie können wahrscheinlich eine Antwort finden, wenn Sie die Implementierung der std :: tr1 :: Funktion betrachten, die Sie einschließen.

In C++ 11 ist Std :: Funktion mit Variadic Vorlagen implementiert. Unter Verwendung eines solchen Vorlagen können Sie Ihre Timer-Klasse aussehen

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

Eine Lösung Makros und Vorlagen: Sie können beispielsweise

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

Vor und nach dem Aufruf von foo() der Wrapper-Funktionen beginWrap() wickeln wollen und endWrap() sollte aufgerufen werden. (Mit endWrap() eine Template-Funktion.)

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

Das Makro

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

verwendet den Vorrang des Komma-Operator beginWrap() wird zuerst genannt zu gewährleisten. Das Ergebnis von f wird an endWrap() übergeben, das es nur zurückgibt. So ist die Ausgabe:

beginWrap() 
foo 10.100000 
endWrap() 

Und das Ergebnis r enthält 10.1.

Verwandte Themen