ما هي أبسط طريقة لإنشاء طريقة فئة واستدعاءها ديناميكيًا في C++؟

StackOverflow https://stackoverflow.com/questions/405432

سؤال

أريد ملء الخريطة باسم الفئة والطريقة ومعرف فريد ومؤشر للطريقة.

typedef std::map<std::string, std::string, std::string, int> actions_type;
typedef actions_type::iterator actions_iterator;

actions_type actions;
actions.insert(make_pair(class_name, attribute_name, identifier, method_pointer));

//after which I want call the appropriate method in the loop

while (the_app_is_running)
{
    std::string requested_class = get_requested_class();
    std::string requested_method = get_requested_method();

    //determine class
    for(actions_iterator ita = actions.begin(); ita != actions.end(); ++ita)
    {
        if (ita->first == requested_class && ita->second == requested_method)
        {
            //class and method match
            //create a new class instance
            //call method
        }
    }
}

إذا كانت الطريقة ثابتة ، فسيكون مؤشر بسيط كافياً والمشكلة بسيطة ، لكنني أريد إنشاء كائن ديناميكيًا ، لذا أحتاج إلى تخزين مؤشر إلى الفصل وإزاحة للطريقة ولا أعرف ما إذا كان هذا يعمل ( إذا كان الإزاحة دائمًا ما يكون هو نفسه).

المشكلة هي أن C++ تفتقر إلى الانعكاس، فالرمز المكافئ في اللغة المفسرة مع الانعكاس يجب أن يبدو هكذا (مثال في PHP):

$actions = array
(
     "first_identifier" => array("Class1","method1"),
     "second_identifier" => array("Class2","method2"),
     "third_identifier" => array("Class3","method3")
);

while ($the_app_is_running)
{
     $id = get_identifier();

     foreach($actions as $identifier => $action)
     {
         if ($id == $identifier)
         {
             $className = $action[0];
             $methodName = $action[1];

             $object = new $className() ;

             $method = new ReflectionMethod($className , $methodName);
             $method -> invoke($object);    
         }
     }
 }

ملاحظة:نعم أحاول إنشاء وحدة تحكم أمامية MVC (على الويب) في C++.أعلم أنني أعرف لماذا لا تستخدم PHP وRuby وPython (أدخل لغة الويب المفضلة لديك هنا) وما إلى ذلك؟، أريد فقط C++.

هل كانت مفيدة؟

المحلول

ولقد كتبت هذه الأشياء ساعة الماضية، وإضافته إلى مجموعتي من الاشياء المفيدة. الشيء الأكثر صعوبة هو التعامل مع وظيفة المصنع، وإذا أنواع تريد إنشاء لا ترتبط بأي شكل من الأشكال. أنا استخدم boost::variant لهذا الغرض. عليك أن تقدم له مجموعة من أنواع تريد من أي وقت مضى للاستخدام. ثم أنها سوف تبقي تتبع ما هو الحالي "النشط" نوع في المتغير. (دفعة :: البديل هو ما يسمى الاتحاد تمييز). والمشكلة الثانية هي كيفية تخزين المؤشرات الدالة. والمشكلة هي أن مؤشر عضوا في A لا يمكن تخزينها لمؤشر عضوا في B. تلك الأنواع غير متوافقة. لحل هذه المشكلة، ط تخزين مؤشرات الدالة في كائن الزائدة operator() ويأخذ دفعة :: البديل:

return_type operator()(variant<possible types...>)

وبطبيعة الحال، لها وظائف كل ما تبذلونه من أنواع "لديها نفس نوع الإرجاع. وإلا فإن المباراة بأكملها من شأنه أن يجعل فقط معنى. الآن رمز:

#include <boost/variant.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/function_types/function_arity.hpp>
#include <boost/preprocessor/repetition.hpp>
#include <map>
#include <string>
#include <iostream>

// three totally unrelated classes
// 
struct foo {
    std::string one() {
        return "I ";
    }
};

struct bar {
    std::string two() {
        return "am ";
    }
};

struct baz {
    std::string three() const {
        return "happy!";
    }
};

// The following are the parameters you have to set
//

// return type
typedef std::string return_type;
// variant storing an object. It contains the list of possible types you
// can store.
typedef boost::variant< foo, bar, baz > variant_type;
// type used to call a function on the object currently active in
// the given variant
typedef boost::function<return_type (variant_type&)> variant_call_type;

// returned variant will know what type is stored. C++ got no reflection, 
// so we have to have a function that returns the correct type based on
// compile time knowledge (here it's the template parameter)
template<typename Class>
variant_type factory() {
    return Class();
}

namespace detail {
namespace fn = boost::function_types;
namespace mpl = boost::mpl;

// transforms T to a boost::bind
template<typename T>
struct build_caller {
    // type of this pointer, pointer removed, possibly cv qualified. 
    typedef typename mpl::at_c<
        fn::parameter_types< T, mpl::identity<mpl::_> >,
        0>::type actual_type;

    // type of boost::get we use
    typedef actual_type& (*get_type)(variant_type&);

// prints _2 if n is 0
#define PLACEHOLDER_print(z, n, unused) BOOST_PP_CAT(_, BOOST_PP_ADD(n, 2))
#define GET_print(z, n, unused)                                         \
    template<typename U>                                                \
    static variant_call_type get(                                       \
        typename boost::enable_if_c<fn::function_arity<U>::value ==     \
            BOOST_PP_INC(n), U>::type t                                 \
        ) {                                                             \
        /* (boost::get<actual_type>(some_variant).*t)(n1,...,nN) */     \
        return boost::bind(                                             \
            t, boost::bind(                                             \
                (get_type)&boost::get<actual_type>,                     \
                _1) BOOST_PP_ENUM_TRAILING(n, PLACEHOLDER_print, ~)     \
            );                                                          \
    }

// generate functions for up to 8 parameters
BOOST_PP_REPEAT(9, GET_print, ~)

#undef GET_print
#undef PLACEHOLDER_print

};

}

// incoming type T is a member function type. we return a boost::bind object that
// will call boost::get on the variant passed and calls the member function
template<typename T>
variant_call_type make_caller(T t) {
    return detail::build_caller<T>::template get<T>(t);
}

// actions stuff. maps an id to a class and method.
typedef std::map<std::string, 
                 std::pair< std::string, std::string >
                 > actions_type;

// this map maps (class, method) => (factory, function pointer)
typedef variant_type (*factory_function)();
typedef std::map< std::pair<std::string,      std::string>, 
                  std::pair<factory_function, variant_call_type> 
                  > class_method_map_type;

// this will be our test function. it's supplied with the actions map, 
// and the factory map
std::string test(std::string const& id,
                 actions_type& actions, class_method_map_type& factory) {
    // pair containing the class and method name to call
    std::pair<std::string, std::string> const& class_method =
        actions[id];

    // real code should take the maps by const parameter and use
    // the find function of std::map to lookup the values, and store
    // results of factory lookups. we try to be as short as possible. 
    variant_type v(factory[class_method].first());

    // execute the function associated, giving it the object created
    return factory[class_method].second(v);
}

int main() {
    // possible actions
    actions_type actions;
    actions["first"] = std::make_pair("foo", "one");
    actions["second"] = std::make_pair("bar", "two");
    actions["third"] = std::make_pair("baz", "three");

    // connect the strings to the actual entities. This is the actual
    // heart of everything.
    class_method_map_type factory_map;
    factory_map[actions["first"]] = 
        std::make_pair(&factory<foo>, make_caller(&foo::one));
    factory_map[actions["second"]] = 
        std::make_pair(&factory<bar>, make_caller(&bar::two));
    factory_map[actions["third"]] = 
        std::make_pair(&factory<baz>, make_caller(&baz::three));

    // outputs "I am happy!"
    std::cout << test("first", actions, factory_map)
              << test("second", actions, factory_map)
              << test("third", actions, factory_map) << std::endl;
}

ويستخدم تقنيات ممتعة جدا من دفعة المعالج، وأنواع وظيفة ومكتبة الربط. قد معقدة حلقة، ولكن إذا كنت تحصل على مفاتيح في هذا الرمز، انها ليست الكثير لفهم بعد الآن. إذا كنت ترغب في تغيير عدد معلمة، عليك أن قرص variant_call_type:

typedef boost::function<return_type (variant_type&, int)> variant_call_type;

والآن يمكنك استدعاء وظائف الأعضاء التي تأخذ عدد صحيح. هنا هو كيف يمكن للجانب الدعوة ستنظر:

return factory[class_method].second(v, 42);

والمتعة!


إذا أنت تقول الآن ما سبق هو معقد جدا، ولدي إلى أتفق معك. أنه هو معقد لأن C ++ هو لا واقع الأمر لمثل هذا الاستخدام الديناميكي. إذا كنت يمكن أن يكون منهجك تجميع وتنفيذها في كل الكائن الذي تريد إنشاء، يمكنك استخدام وظائف افتراضية خالصة. بدلا من ذلك، هل يمكن رمي بعض استثناء (مثل الأمراض المنقولة جنسيا :: runtime_error) في تنفيذ الافتراضي، الفئات المشتقة بحيث لا تحتاج إلى تنفيذ كل شيء:

struct my_object {
    typedef std::string return_type;

    virtual ~my_object() { }
    virtual std::string one() { not_implemented(); }
    virtual std::string two() { not_implemented(); }
private:
   void not_implemented() { throw std::runtime_error("not implemented"); }
};

لإنشاء كائنات، ومصنع المعتاد القيام به

struct object_factory {
    boost::shared_ptr<my_object> create_instance(std::string const& name) {
        // ...
    }
};

ويمكن أن تتألف الخريطة عن طريق خريطة رسم الخرائط معرفات لزوج من الصف واسم الدالة (نفس مثل أعلاه)، وتعيين الخريطة التي لدفعة :: وظيفة:

typedef boost::function<my_object::return_type(my_object&)> function_type;
typedef std::map< std::pair<std::string, std::string>, function_type> 
                  class_method_map_type;
class_method_map[actions["first"]] = &my_object::one;
class_method_map[actions["second"]] = &my_object::two;

وسوف استدعاء وظيفة عمل من هذا القبيل:

boost::shared_ptr<my_object> p(get_factory().
    create_instance(actions["first"].first));
std::cout << class_method_map[actions["first"]](*p);

وبطبيعة الحال، مع هذا النهج، لك المرونة فضفاضة و(ربما لم محة) كفاءة، ولكن يمكنك تبسيط كبير التصميم الخاص بك.

نصائح أخرى

ربما كنت تبحث عنه مؤشرات وظيفة الأعضاء.

الاستخدام الأساسي:

class MyClass
{
    public:
        void function();
};

void (MyClass:*function_ptr)() = MyClass::function;

MyClass instance;

instance.*function_ptr;

كما هو مذكور في الأسئلة الشائعة حول C++ Lite، فإن وحدات الماكرو و typedefs من شأنه أن يزيد بشكل كبير من سهولة القراءة عند استخدام مؤشرات وظيفة العضو (لأن بناء الجملة الخاص بها ليس شائعًا في التعليمات البرمجية).

وأعتقد أن أهم شيء لمعرفة هنا هو، هل كل الطرق لديكم نفس التوقيع؟ إذا فعلوا ذلك، هذا هو استخدام تافهة من دفعة ربط (إذا كنت في ذلك)، [فونكتورس] هي خيار (وثابت، بطة نوع نوع)، أو الميراث الظاهري مجرد أوله هو خيار. الميراث إيسنت حاليا في رواج ولكن من السهل جدا أن يفهم، وأنا لا أعتقد أنه يعقد الأمور بعد الآن ثم استخدام دفعة ربط (إيمهو أفضل ل[فونكتورس] صغيرة غير النظامية).

وهنا هو تطبيق نموذج

#include<iostream>
#include<map>
#include<string>

using std::map;
using std::string;
using std::cout;
using std::pair;

class MVCHandler
{
public:
    virtual void operator()(const string& somekindofrequestinfo) = 0;
};

class MyMVCHandler : public MVCHandler
{
public:
    virtual void operator()(const string& somekindofrequestinfo)
    {
        cout<<somekindofrequestinfo;
    }
};

void main()
{
    MyMVCHandler myhandler;
    map<string, MVCHandler*> handlerMap;
    handlerMap.insert(pair<string, MVCHandler*>("mysuperhandler", &myhandler));
    (*handlerMap["mysuperhandler"])("somekindofrequestdata");
}

وعلى غرار العديد من الأسئلة C ++، وهذا يشبه تطبيق آخر من دفعة. تريد أساسا لتخزين نتيجة دفعة :: ربط (والفئة :: الأعضاء، وجوه). [تحرير] تخزين مثل هذه النتيجة هو سهل مع دفعة :: وظيفة.

ويمكنك محاولة استخدام المصنع أو مجردة تصميم مصنع أنماط الطبقة، ومؤشر دالة على وظيفة.

ولقد وجدت ما يلي 2 صفحات الويب مع تطبيقات عندما كنت تبحث عن حلول لمشكلة مماثلة:

مصنع

المستخلص مصنع

إذا كنت لا تريد استخدام <لأ href = "https://stackoverflow.com/questions/405432/what-is-the-simplest-way-to-create-and-call-dynamically-a-class -method في وج # 405452 "> عضوا مؤشرات الدالة ، يمكنك استخدام احصائيات التي تأخذ حجة من مثيل فئة. على سبيل المثال:

class MyClass
{
    public:
        void function();

        static void call_function(MyClass *instance);  // Or you can use a reference here.
};

MyClass instance;
MyClass::call_function(&instance);

وهذا يتطلب المزيد من العمل على المبرمج ويسبب مشاكل الصيانة (لأنه إذا قمت بتحديث توقيع واحد، يجب تحديث هذا الآخر أيضا).

ويمكنك أيضا استخدام وظيفة ثابتة واحدة والذي يدعو جميع وظائف العضو الخاص بك:

class MyClass
{
    public:
        enum Method
        {
            fp_function,
        };

        void function();

        static void invoke_method(MyClass *instance, Method method);  // Or you can use a reference here.
};

void MyClass::invoke_method(MyClass *instance, Method method)
{
    switch(method)
    {
        default:
            // Error or something here.
            return;

        case fp_function:
            instance->function();
            break;

        // Or, if you have a lot of methods:

#define METHOD_CASE(x) case fp_##x: instance->x(); break;

        METHOD_CASE(function);

#undef METHOD_CASE
    }

    // Free logging!  =D
}

MyClass instance;
MyClass::invoke_method(instance, MyClass::fp_function);

ويمكنك أيضا استخدام تحميل الديناميكي من وظائف هي:

استخدم GetProcAddress في ويندوز، وdlsym في يونكس.

والذهاب للموضوع، المراقب نمط التصميم.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top