3

クラス名とメソッド、一意の識別子、およびメソッドへのポインターでマップを埋めたいと思います。

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

PS: はい、C++ で (Web) MVC フロント コントローラーを作成しようとしています。PHP、Ruby、Python (お気に入りの Web 言語をここに挿入) などを使用しない理由はわかっていますが、C++ が必要なだけです。

4

8 に答える 8

7

おそらく、メンバー関数ポインターを探しているのでしょう。

基本的な使い方:

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

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

MyClass instance;

instance.*function_ptr;

C++ FAQ Lite で述べられているように、マクロとtypedefs は、メンバー関数ポインターを使用するときに読みやすさを大幅に向上させます (それらの構文はコードで一般的ではないため)。

于 2009-01-01T20:37:44.510 に答える
5

私はそれを数時間前に書き、有用なもののコレクションに追加しました。最も難しいのは、作成したいタイプがまったく関連していない場合、ファクトリ関数に対処することです。これには a を使用しboost::variantました。使用したいタイプのセットを指定する必要があります。次に、バリアントで現在「アクティブな」タイプが何であるかを追跡します。(boost::variant は、いわゆる判別共用体です)。2 番目の問題は、関数ポインターを格納する方法です。問題は、 のメンバーAへのポインターを のメンバーへのポインターに格納できないことですB。これらのタイプは互換性がありません。operator()これを解決するために、オーバーロードして boost::variant を取るオブジェクトに関数ポインターを格納します。

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;

これで、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 をクラス名と関数名のペアにマッピングするマップ (上記と同じ) と、それを boost::function にマッピングするマップによって構成できます。

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

もちろん、このアプローチでは、柔軟性と (プロファイルを作成していない可能性があります) 効率が失われますが、設計は大幅に簡素化されます。

于 2009-01-02T16:18:40.560 に答える
3

ここで確認すべき最も重要なことは、すべてのメソッドが同じ署名を持っているかということです。もしそうなら、これはブーストバインドの些細な使用です(あなたがそれに興味があるなら)、ファンクターはオプションです(静的なダックタイプの種類)、または単なる単純な仮想継承がオプションです。継承は現在流行ではありませんが、理解するのは非常に簡単で、ブーストバインドを使用するよりも物事が複雑になるとは思いません(小さな非システムファンクターに最適です)。

ここにサンプル実装があります

#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");
}
于 2009-01-02T17:31:50.803 に答える
2

多くの C++ の質問と同様に、これは Boost の別のアプリケーションのように見えます。基本的には、boost::bind(&Class::member, &Object) の結果を保存します。[編集] このような結果の保存は、boost::function を使えば簡単です。

于 2009-01-02T10:08:04.907 に答える
1

クラスにはファクトリまたは抽象ファクトリのデザイン パターンを使用し、関数には関数ポインタを使用してみてください。

同様の問題の解決策を探していたときに、実装を含む次の 2 つの Web ページを見つけました。

工場

抽象工場

于 2009-01-01T20:53:59.690 に答える
1

メンバ関数ポインタを使用したくない場合は、クラス インスタンスの引数を取る static を使用できます。例えば:

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);
于 2009-01-01T20:54:09.657 に答える
0

関数の動的ロードを使用することもできます。

Windows では GetProcAddress を使用し、Unix では dlsym を使用します。

于 2009-01-01T21:05:22.800 に答える
0

Subject-Observer 設計パターンに進みます。

于 2009-01-02T01:44:39.010 に答える