2010-01-25 5 views
8

J'essaie d'écrire une classe ou une fonction wrapper qui me permet d'exécuter du code avant et après la fonction wrapped. Idéalement, l'encapsuleur doit être générique, travaillant pour les fonctions et les fonctions membres, avec n'importe quelle signature.Comment écrire un wrapper sur les fonctions et les fonctions membres qui exécutent du code avant et après la fonction encapsulée?

Plus d'info:

Je suis à la recherche d'un moyen simple pour libérer/obtenir à nouveau la GIL autour de mon cher C++ appelle sans avoir à écrire manuellement des emballages minces comme ceci:

float foo_wrapper(int x, float y) 
{ 
    Py_BEGIN_ALLOW_THREADS 
    int result = foo(x, y); 
    Py_END_ALLOW_THREADS 
    return result; 
} 

BOOST_PYTHON_MODULE(test) 
{ 
    boost::python::def("foo", &foo_wrapper); 
} 

Cette Le type de wrapper sera répété plusieurs fois pour toutes sortes de fonctions, et j'aimerais trouver une solution qui me permettrait d'éviter de tout coder.

J'ai essayé quelques approches, mais le mieux que je pouvais venir avec nécessaire à l'utilisateur d'indiquer explicitement les types de valeurs et paramètres retour, comme:

boost::python::def("foo", &wrap_gil<float, int, float>(&foo_wrapper)); 

Mais il me semble qu'il devrait être possible de passez simplement le pointeur sur la fonction (& foo_wrapper) et laissez le compilateur déterminer les types.

Est-ce que quelqu'un connaît une technique que je pourrais utiliser ou diriger dans la bonne direction?

À la votre!

Répondre

11

Dans ce cas, vous pouvez écrire une classe Functor qui recouvre votre fonction, puis surcharger boost :: python :: detail :: get_signature pour accepter votre Functor!

MISE À JOUR: Ajout du support pour les fonctions membres aussi!

Exemple:

#include <boost/shared_ptr.hpp> 
#include <boost/python.hpp> 
#include <boost/python/signature.hpp> 
#include <boost/mpl/vector.hpp> 

#include <iostream> 
#include <string> 
#include <sstream> 

static boost::shared_ptr<std::ostringstream> test_stream_data; 

std::ostringstream& test_stream() 
{ 
    if (!test_stream_data) { 
     test_stream_data.reset(new std::ostringstream); 
    } 
    return *test_stream_data; 
} 


std::string get_value_and_clear_test_stream() 
{ 
    std::string result; 
    if (test_stream_data) { 
     result = test_stream_data->str(); 
    } 
    test_stream_data.reset(new std::ostringstream); 
    return result; 
} 


std::string func(int a, double b) 
{ 
    std::ostringstream oss; 
    oss << "func(a=" << a << ", b=" << b << ")"; 
    std::string result = oss.str(); 
    test_stream() << "- In " << result << std::endl; 
    return result; 
} 


class MyClass 
{ 
public: 
    MyClass(std::string p_name) 
     : m_name(p_name) 
    { 
     test_stream() << "- In MyClass::MyClass(p_name=\"" << p_name << "\")" << std::endl; 
    } 

    MyClass(MyClass const& p_another) 
     : m_name(p_another.m_name) 
    { 
     test_stream() 
      << "- In MyClass::MyClass(p_another=MyClass(\"" 
      << p_another.m_name << "\"))" << std::endl; 
    } 

    ~MyClass() 
    { 
     test_stream() << "- In MyClass(\"" << this->m_name << "\")::~MyClass()" << std::endl; 
    } 

    boost::shared_ptr<MyClass> clone_and_change(std::string p_new_name) 
    { 
     test_stream() 
      << "- In MyClass(\"" << this->m_name << "\").clone_and_change(p_new_name=\"" 
      << p_new_name << "\")" << std::endl; 

     boost::shared_ptr<MyClass> result(new MyClass(*this)); 
     result->m_name = p_new_name; 

     return result; 
    } 

    std::string get_name() 
    { 
     test_stream() << "- In MyClass(\"" << this->m_name << "\").get_name()" << std::endl; 
     return this->m_name; 
    } 

    std::string m_name; 
}; 


struct ScopePreAndPostActions 
{ 
    ScopePreAndPostActions() 
    { 
     test_stream() << "[Before action...]" << std::endl; 
    } 

    ~ScopePreAndPostActions() 
    { 
     test_stream() << "[After action...]" << std::endl; 
    } 
}; 





template <class FuncType_> 
struct FuncWrapper; 

// You can code-generate specializations for other arities... 

template <class R_, class A0_, class A1_> 
struct FuncWrapper<R_ (A0_, A1_)> 
{ 
    typedef R_ (*func_type)(A0_, A1_); 

    typedef typename boost::add_const<typename boost::add_reference<typename A0_>::type>::type AC0_; 
    typedef typename boost::add_const<typename boost::add_reference<typename A1_>::type>::type AC1_; 

    func_type m_wrapped_func; 

    FuncWrapper(func_type p_wrapped_func) 
     : m_wrapped_func(p_wrapped_func) 
    { 
    } 

    R_ operator()(AC0_ p0, AC1_ p1) 
    { 
     ScopePreAndPostActions actions_guard; 
     return this->m_wrapped_func(p0, p1); 
    } 
}; 

template < 
    class R_, 
    class C_, 
    class A0_=void, 
    class A1_=void, 
    class A2_=void 
    // ... 
> 
struct MemberFuncWrapper; 

template <class R_, class C_, class A0_> 
struct MemberFuncWrapper<R_, C_, A0_> 
{ 
    typedef R_ (C_::*member_func_type)(A0_); 

    typedef typename boost::add_const<typename boost::add_reference<typename A0_>::type>::type AC0_; 

    member_func_type m_wrapped_method; 

    MemberFuncWrapper(member_func_type p_wrapped_method) 
     : m_wrapped_method(p_wrapped_method) 
    { 
    } 

    R_ operator()(C_* p_self, AC0_ p0) 
    { 
     ScopePreAndPostActions actions_guard; 
     return (p_self->*(this->m_wrapped_method))(p0); 
     return R_(); 
    } 
}; 



namespace boost { namespace python { namespace detail { 

    // You can code-generate specializations for other arities... 

    template <class R_, class P0_, class P1_> 
    inline boost::mpl::vector<R_, P0_, P1_> 
    get_signature(FuncWrapper<R_ (P0_, P1_)>, void* = 0) 
    { 
     return boost::mpl::vector<R_, P0_, P1_>(); 
    } 

    template <class R_, class C_, class P0_> 
    inline boost::mpl::vector<R_, C_*, P0_> 
    get_signature(MemberFuncWrapper<R_, C_, P0_>, void* = 0) 
    { 
     return boost::mpl::vector<R_, C_*, P0_>(); 
    } 

} } } 

// ------------------------------------------------------------------- 

template <class FuncPtr_> 
void make_wrapper(FuncPtr_); 

// You can code-generate specializations for other arities... 

template <class R_, class A0_, class A1_> 
FuncWrapper<R_ (A0_, A1_)> make_wrapper(R_ (*p_wrapped_func)(A0_, A1_)) 
{ 
    return FuncWrapper<R_ (A0_, A1_)>(p_wrapped_func); 
} 

template <class R_, class C_, class A0_> 
MemberFuncWrapper<R_, C_, A0_> make_wrapper(R_ (C_::*p_wrapped_method)(A0_)) 
{ 
    return MemberFuncWrapper<R_, C_, A0_>(p_wrapped_method); 
} 

template <class R_, class C_, class A0_, class A1_> 
MemberFuncWrapper<R_, C_, A0_, A1_> make_wrapper(R_ (C_::*p_wrapped_method)(A0_, A1_)) 
{ 
    return MemberFuncWrapper<R_, C_, A0_, A1_>(p_wrapped_method); 
} 


using namespace boost::python; 

void RegisterTestWrapper() 
{ 
    def("GetValueAndClearTestStream", &get_value_and_clear_test_stream); 
    def("TestFunc", &func); 
    def(
     "TestWrappedFunctor", 
     make_wrapper(&func) 
    ); 

    { 
     class_< MyClass, shared_ptr<MyClass>, boost::noncopyable > c("MyClass", init<std::string>()); 
     c.def("CloneAndChange", &MyClass::clone_and_change); 
     c.def("GetName", &MyClass::get_name); 
     c.def("WrappedCloneAndChange", make_wrapper(&MyClass::clone_and_change)); 
    } 
} 

Et python:

import unittest 
from _test_wrapper import GetValueAndClearTestStream, TestFunc, TestWrappedFunctor, MyClass 

class Test(unittest.TestCase): 

    def setUp(self): 
     GetValueAndClearTestStream() 

    def testWrapper(self): 
     self.assertEqual(TestFunc(69, 1.618), 'func(a=69, b=1.618)') 
     self.assertEqual(GetValueAndClearTestStream(), '- In func(a=69, b=1.618)\n') 

     self.assertEqual(TestWrappedFunctor(69, 1.618), 'func(a=69, b=1.618)') 
     self.assertEqual(
      GetValueAndClearTestStream(), 
      (
       '[Before action...]\n' 
       '- In func(a=69, b=1.618)\n' 
       '[After action...]\n' 
      ), 
     ) 

def testWrappedMemberFunction(self): 
    from textwrap import dedent 
    x = MyClass("xx") 
    y = x.WrappedCloneAndChange("yy") 
    z = y.WrappedCloneAndChange("zz") 

    self.assertEqual(x.GetName(), "xx") 
    self.assertEqual(y.GetName(), "yy") 
    self.assertEqual(z.GetName(), "zz") 

    self.assertEqual(
     GetValueAndClearTestStream(), 
     dedent('''\ 
     - In MyClass::MyClass(p_name="xx") 
     [Before action...] 
     - In MyClass("xx").clone_and_change(p_new_name="yy") 
     - In MyClass::MyClass(p_another=MyClass("xx")) 
     [After action...] 
     [Before action...] 
     - In MyClass("yy").clone_and_change(p_new_name="zz") 
     - In MyClass::MyClass(p_another=MyClass("yy")) 
     [After action...] 
     - In MyClass("xx").get_name() 
     - In MyClass("yy").get_name() 
     - In MyClass("zz").get_name() 
     '''), 
    ) 
+1

solution parfaite! Merci beaucoup! =) –

+1

Vous pouvez ajouter uniquement des références au lieu de références const aux paramètres, de sorte qu'ils ne gâcheront pas la constance. –

+0

Merci pour cette excellente solution! Je sais que c'est un vieux fil de discussion mais au cas où quelqu'un d'autre le rencontrerait, je pense qu'il y a une correction à apporter. Pour le 'AC0_',' AC1_', etc typedefs je pense que vous voulez échanger les emplacements de 'add_const' et' add_reference'. La façon dont cela est fait dans l'extrait, le 'add_const' ne semble pas avoir d'effet. – histumness

0

Avez-vous examiné la technique d'enveloppement des fonctions décrite par Stroustrup dans son article "Wrapping C++ Member Function Calls"? Il y a aussi une réponse SO here qui montre comment l'implémenter de manière concise. Fondamentalement, vous implémenter un modèle qui surcharge operator->(). Dans cet implémentation de operator vous construisez un objet temporaire avant votre appel de fonction réel. Le constructeur et le destructeur de l'objet temporaire prennent soin d'appeler votre code "pre-" et "post-" avant et après votre appel de fonction, respectivement.

Questions connexes