Как написать оболочку для функций и функций-членов, которая выполняет некоторый код до и после функции-оболочки? - PullRequest
8 голосов
/ 25 января 2010

Я пытаюсь написать некоторый класс-оболочку или функцию, которая позволяет мне выполнять некоторый код до и после упакованной функции.

float foo(int x, float y)
{
    return x * y;
}

BOOST_PYTHON_MODULE(test)
{
     boost::python::def("foo", <somehow wrap "&foo">);
}

В идеале оболочка должна быть универсальной, работать как для функций, так и для функций-членов с любой сигнатурой.

Дополнительная информация:

Я ищу простой способ освободить / повторно получить GIL вокруг моих дорогих вызовов C ++ без необходимости писать тонкие обертки вручную, как это:

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

Этот вид оболочки будет повторяться несколько раз для всех видов функций, и я хотел бы найти решение, которое позволило бы мне избежать кодирования всех из них.

Я пробовал некоторые подходы, но лучшее, что я мог получить, требовало от пользователя явного указания типов возвращаемых значений и параметров, например:

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

Но мне кажется, что можно просто передать указатель на функцию (& foo_wrapper) и позволить компилятору выяснить типы.

Кто-нибудь знает технику, которую я мог бы использовать, или указывает мне правильное направление?

Ура!

Ответы [ 2 ]

11 голосов
/ 26 января 2010

В этом случае вы можете написать класс Functor, который обернет вашу функцию, а затем перегрузить boost :: python :: detail :: get_signature для принятия вашего Functor!

ОБНОВЛЕНИЕ: добавлена ​​поддержка функций-членов!

Пример:

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

А на питоне:

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()
        '''),
    )
0 голосов
/ 26 января 2010

Рассматривали ли вы метод обтекания функций, описанный Страуструпом в его статье " Обтекание вызовов функций C ++ "? Также есть ответ SO здесь , который демонстрирует, как реализовать его в сжатой форме. По сути, вы бы реализовали шаблон, который перегружает operator->(). В реализации этого operator вы создадите временный объект до фактического вызова функции. Конструктор и деструктор временного объекта позаботятся о вызове вашего кода " pre- " и " post- " до и после фактического вызова функции, соответственно.

...