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

추신 : 예, C ++에서 (웹) MVC 프론트 컨트롤러를 만들려고합니다. PHP, Ruby, Python을 사용하지 않는 이유를 알고 있습니다 (여기에 좋아하는 웹 언어를 여기에 삽입하십시오) 등은 C ++를 원합니다.

도움이 되었습니까?

해결책

나는 지난 몇 시간 동안 그 물건을 썼고, 유용한 것들 컬렉션에 추가했습니다. 가장 어려운 것은 생성하려는 유형이 어떤 식 으로든 관련이없는 경우 공장 기능에 대처하는 것입니다. 나는 a를 사용했다 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;
}

Boost Preprocessor, 기능 유형 및 BIND 라이브러리의 꽤 재미있는 기술을 사용합니다. 복잡 할 수도 있지만 해당 코드의 키를 얻는다면 더 이상 이해하는 것은 그리 많지 않습니다. 매개 변수 수를 변경하려면 variant_call_type를 조정하면됩니다.

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

이제 int를 취하는 멤버 기능을 호출 할 수 있습니다. 호출 측면이 어떻게 보이는지 다음과 같습니다.

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

재미있게 보내세요!


위의 것이 너무 복잡하다고 말하면, 나는 당신과 동의해야합니다. 그것 ~이다 C ++이기 때문에 복잡합니다 ~ 아니다 실제로 그러한 역동적 인 사용을 위해 만들어졌습니다. 생성하려는 각 객체에서 메소드를 그룹화하고 구현할 수있는 경우 순수한 가상 함수를 사용할 수 있습니다. 또는 기본 구현에서 std :: 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) {
        // ...
    }
};

맵은 맵 매핑 ID를 한 쌍의 클래스 및 함수 이름 (위와 같은 것과 같은)으로 구성하고 부스트 :: 함수에 대한 맵 맵핑으로 구성 될 수 있습니다.

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 ++ FAQ 라이트, 매크로 및 typedef멤버 함수 포인터를 사용할 때 S는 조정이 일반적이지 않기 때문에 가독성을 크게 증가시킵니다.

여기서 알아야 할 가장 중요한 것은 모든 방법이 동일한 서명을 가지고 있다고 생각합니다. 그들이 그렇게한다면, 이것은 Boost Bind의 사소한 사용 (당신이 그것에 속하는 경우), functors는 옵션 (정적, 오리 유형)이거나 일반적인 가상 상속이 옵션입니다. 상속은 현재 유행하지는 않지만 이해하기 쉽고 이해하기 쉽고 더 이상 물건을 복잡하게 생각하지 않는다고 생각하지 않습니다. Boost Bind (IMHO는 소형 비 전신 기능에 가장 적합).

다음은 샘플 구현입니다

#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 ++ 질문과 마찬가지로 이것은 또 다른 부스트의 응용 프로그램처럼 보입니다. 기본적으로 boost :: bind (& class :: member, & object)의 결과를 저장하려고합니다. [편집] Boost :: Function에서는 이러한 결과를 쉽게 저장합니다.

클래스에 공장 또는 추상 공장 디자인 패턴을 사용하고 기능에 대한 기능 포인터를 사용해 볼 수 있습니다.

비슷한 문제에 대한 솔루션을 검색 할 때 구현이 포함 된 다음 2 개의 웹 페이지를 찾았습니다.

공장

초록 공장

사용하고 싶지 않은 경우 멤버 기능 포인터, 당신은 클래스 인스턴스의 논증을 취하는 정적을 사용할 수 있습니다. 예를 들어:

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

기능의 동적 로딩을 사용할 수도 있습니다.

Windows에서 GetProcadDress를 사용하고 UNIX에서 DLSYM을 사용하십시오.

주제 관찰자 디자인 패턴으로 이동하십시오.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top