1

みなさん、こんにちは!

私の質問は次のとおりです。

他のクラスのメソッドへのポインタを格納するテンプレートクラスがあります(もちろん、テンプレートクラスは、渡されるクラスのタイプを最初は認識していません)。次のように、渡されたクラスへのポインターと、格納したい渡されたクラスのメソッドのアドレスである別の引数を取得するように、テンプレートクラスのコンストラクターを作成しました。

    template <typename MyClass>
    TemplateClass
    {
    typedef void (MyClass::*MethodPointer)();
    MyClass* theClass;
    MethodPointer methodPointer;
public:
    TemplateClass(MyClass* cl, MethodPointer func) : theClass(cl), methodPointer(func){}

    void Excute()
    {
        return (theClass->*methodPointer)();
    }
    };

次に、基本クラスを作成し、そこからサブクラスを派生させました。

    class BaseClass
    {
        // other details are omitted...
        virtual void DoSomething();
    };

    class DerivedClass : public BaseClass
    {
        // other details are omitted...
        void DoSomething();
    };

    // the definitions of the methods is omitted because it had no relevance to the issue

次に、基本クラスをパラメータとして次のようなテンプレートクラスのtypedefを作成しました。

    typedef TemplateClass<BaseClass> TypedTemplateClass;

次に、派生クラスのポインターをTypedTemplateClassのコンストラクターに渡そうとすると、コンパイラーは、次のように、引数を派生クラスから基本クラスに変換できないと言います。

    DerivedClass* myDerivedObject = new DerivedClass();
    TypedTemplateClass* myTemplateObject = new TypedTemplateClass(myDerivedObject, &DerivedClass::DoSomething);

しかし、基本クラス自体のオブジェクトを渡すと、すべてがうまく機能します。次のように:

    BaseClass* baseObject = new BaseClass();
    TypedTemplateClass* myTemplateObject2 = new TypedTemplateClass(baseObject, &BaseClass::DoSomething);

それで、誰かがこの問題を克服するために私を啓発することができますか?問題は、型指定されたテンプレートクラスが基本クラスオブジェクトを期待していることにあることを認識していますが、派生クラスオブジェクトを渡す必要があります-異なる派生クラスを作成し、それらのメソッドをテンプレートクラスに多形的に渡すことができるためです。また、TypedTemplateClass定義を無視して、それぞれ異なる派生クラスタイプを持つテンプレートクラスオブジェクトを作成できることも認識しています。しかし、上記の提案は私の意図です。

VisualStudioIDEを使用しています

ご関心とご協力をよろしくお願いいたします。

4

3 に答える 3

4

問題は、ポインタからメソッドへのポインタがベースタイプのポインタからメソッドへのポインタからメソッドへのポインタに変換できないことです。オブジェクトタイプ。

検討:

Base instance;
void (Base::*pmethod)();
pmethod = &Base::doSomething;
(instance.*pmethod)();    // OK

あなたが書くことを許されたなら

pmethod = &Derived::doSomethingElse;

次に、タイプのpmethodを呼び出すためDerived::doSomethingElseに使用できます。instanceBase

リスコフの置換原則の下では、派生オブジェクト(参照)は-ベースオブジェクト(参照)です。これは、ベースに対して実行できることはすべて派生に対して実行できるためですが、派生メソッドへのポインターは-ではありません-ベースのメソッドへのポインタ。確かに、それは逆です(Baseへのポインタは派生したメソッドへのポインタです)。これが、メソッドへのポインタが反変であると言う理由です。

void (Derived::*pmethod)() = &Base::doSomething;

おそらくあなたの場合の最善の策は、テンプレート化されたコンストラクターを作成し、型消去を使用してDerivedメソッドポインターの型を非表示にすることです。以下では

template<typename T>
TemplateClass(T *instance, void (T::*pmethod)());

コンストラクターの型の2つTは、互いにキャンセルして関数のシグニチャーを与えることができますvoid ()std::function<void ()>あなたはメンバーとこれを行うことができます:

Base *instance;
std::function<void ()> type_erased_method;

template<typename T>
TemplateClass(T *instance, void (T::*pmethod)()):
    instance(instance),
    type_erased_method(std::bind(pmethod, instance))
{
}
于 2012-08-31T13:47:28.080 に答える
0

これはどういう意味ですか:TemplateClass(MyClass* class, &MyClass::SomeMethod);有効なC++コードではありません。C ++標準ライブラリを使い続けてみませんか?呼び出し可能オブジェクトを格納する必要がある場合はstd::function、ラムダから構築されたものを使用してください。コンパイラとライブラリが必要なすべての変換を処理します...

于 2012-08-31T14:11:30.630 に答える
0

さて、私が取り組んだプロジェクトは、C ++でイベントを処理するメカニズムをC#のイベントやEventHandlerに似たものにするための試みでした。そこで、オンラインでいくつかのテキストを調べて適切な方法を見つけた後、2つのテンプレートクラスを作成しました。1つはメンバーメソッドへのポインタを格納し、それを「EventHandler」と呼びます。もう1つは、イベントハンドラーのマップを格納し、必要に応じてそれらを呼び出すことです。これは「イベント」クラスです。次に、2つの「通常の」クラスを作成しました。1つはイベント発生クラスで、もう1つは応答クラスまたはリスニングクラスです。EventクラスとEventHandlerクラスの初期バージョンは次のとおりです。

#include <functional>

namespace eventhandling
{
#ifndef __BASIC_EVENT_HANDLER__
#define __BASIC_EVENT_HANDLER__

////////////////////////////////////////////////////////////////////////////
// the root event handler class
////////////////////////////////////////////////////////////////////////////
template <typename empty = int, empty = 0>
class BaseEventHandler
{
public:
    virtual void Excute() = 0;
};


///////////////////////////////////////////////////////////////////////////
// the basic derived event handler class; the class which will wrap the
// methods of other classes which want to respond to specific event(s)..
///////////////////////////////////////////////////////////////////////////
template <typename ResponderType>
class EventHandler : public BaseEventHandler < >
{
    typedef void (ResponderType::*MethodPointer());

    ResponderType* responder;
    MethodPointer methodPointer;

public:
    EventHandler(ResponderType* resp, MethodPointer func) : responder(resp), methodPointer(func)
    {}

    void Excute()
    {
        return methodPointer();
    }
};

#endif
}

#include "BasicEventHandler.h"
#include <map>

namespace eventhandling
{
#ifndef __BASIC_EVENT__
#define __BASIC_EVENT__

////////////////////////////////////////////////////////////////////////////////////////////////
// the event class which will receive these event handlers, stores them in a map object,
// and call them squentially when invoked from within the event firing method...
////////////////////////////////////////////////////////////////////////////////////////////////

// the template takes no parameters, so I added an empty parameter, just because
//it cannot ignore the parameter list, otherwise it will be considered template specialization
template <typename empty = int, empty = 0>
class BasicEvent
{
    //store the eventhandlers in a map so that I can track them from outside the class by id
    typedef std::map<int, BaseEventHandler<empty>* > Responders;
    Responders responders;
    int respondersCount;

public:
    BasicEvent() : respondersCount(0)
    {}

    // classical add method templatized so that it accepts any object
    template <typename Responder>
    int Add(Responder* sender, void (Responder::*memberFunc)())
    {
        responders[respondersCount] = (new EventHandler<Responder>(sender, memberFunc));
        respondersCount++;
        return respondersCount - 1;
    }

    // simple method to clean up the map memory after done with the eventhandlers
    void Remove(int responderID)
    {
        Responders::iterator it = responders.find(responderID);
        if (it == responders.end())
            return;
        delete it->second;
        responders.erase(it);
    }

    // method which invokes all the eventhandlers alltogether without control from the outside
    void Invoke()
    {
        Responders::iterator it = responders.begin();
        for (; it != responders.end(); ++it)
        {
            it->second->Excute();
        }
    }

    // method which invokes only the eventhandler whose id has been passed to it
    void Invoke(int id)
    {
        Responders::iterator it = responders.find(id);
        if (it != responders.end())
            it->second->Excute();
    }

    // overloaded operator+= to replace the functionality of method Add()
    template <typename Responder>
    void operator+=(EventHandler<Responder>* eventhandler)
    {
        responders[respondersCount] = eventhandler;
        respondersCount++;
    }

    // overloaded operator -= to replace the functionality of method Remove()
    void operator-=(int id)
    {
        Responders::iterator it = responders.find(id);
        if (it == responders.end())
            return;
        delete it->second;
        responders.erase(it);
    }

    //simple method which gives the size of the map object
    int Size()
    {
        return respondersCount;
    }
};
#endif
}

次に、新しいEventHandlerオブジェクトを作成するときに、明示的な'&lt;…..>'テンプレート構文を削除したかったのですが、これは明らかにC#の場合であるため、次のように単純に作成しました。

    typedef EventHandler<MyClass> SomeEventFired_EventHandler;

そのため、このテンプレートの新しいオブジェクトを作成する必要がある場合は、次のように記述する必要があります。

    MyClass* myObject = new MyClass();
    MyEventFiringClass* firingObject = new MyEventFiringClass();
    firingObject->OnFired += new SomeEventFired_EventHandler(myObject, &MyClass::SomeMethod);

もちろん、後で完全なコード例を使用すると、より明確になります。ここに私の質問がありました。MyClassの派生クラスのオブジェクトを渡すことができるようにしたかったのです。問題は、上記のEventHandlerのテンプレートがそのような派生オブジェクトを受け入れないことでした。これは、基本クラスのオブジェクトのみを予期しており、コンパイラが派生クラスから基本クラスに変換できないと文句を言ったためです。そして、ここにecatmurの貴重な助けがあります。彼は、EventHandlerクラスのコンストラクターをテンプレート化する適切な方法を教えてくれました。このように、MyClassを基本クラスとして使用して定義済みのSomeEventFired_EventHandlerと入力すると、オブジェクトがMyClassから派生したクラスからのものである限り、任意のオブジェクトとそのメソッドをコンストラクターに渡すことができるようになりました。これは、EventHandlerの多態的な機能を実現するための私の究極の目的でした。この機能が必要だったのは、C#でEventHandlerを確認すると、System :: EventHandlerがポリモーフィックであり、私が推測するように、本質的にクラスObjectから派生したクラスからさまざまなオブジェクトを受け入れることがわかるためです。ですから、これがecatmurソリューションに基づいて修正されたEventHandlerクラスを使用した完全な例であり、皆さんがレビューできるようになっています。最終的に、BaseEventHandlerクラスから派生して、派生したEventHandlerがさまざまな戻り型とさまざまな引数パラメーターを持つメソッドを格納できるようにすることができます。これは、ここに示されている基本的なメソッドがvoidを返し、voidを取得するメソッドを受け入れるためです( std ::function<>の宣言を変更する

std::function<int(int)> 

、等々)。

イベントクラスは上記と同じです…</p>

#include <functional>

namespace eventhandling
{
#ifndef __BASIC_EVENT_HANDLER__
#define __BASIC_EVENT_HANDLER__

////////////////////////////////////////////////////////////////////////////
// the root event handler class
////////////////////////////////////////////////////////////////////////////
template <typename empty = int, empty = 0>
class BaseEventHandler
{
public:
    virtual void Excute() = 0;
};


///////////////////////////////////////////////////////////////////////////
// the basic derived event handler class; the class which will wrap the
// methods of other classes which want to respond to specific event(s)..
///////////////////////////////////////////////////////////////////////////
template <typename ResponderType>
class EventHandler : public BaseEventHandler < >
{
    std::function<void ()> type_erased_method;
    ResponderType* responder;

public:

    template<typename T>
    EventHandler(T* resp, void (T::*MethodPointer)()) : responder(resp), type_erased_method(std::bind(MethodPointer, resp))
    {}

    void Excute()
    {
        return type_erased_method();
    }
};

#endif
}

イベント発生クラスヘッダーファイル…</p>

#include <iostream>
#include <string>
#include "BasicEvent.h"

namespace eventhandling
{
#ifndef __FONT_SIMULATOR__
#define __FONT_SIMULATOR__

typedef BasicEvent<> FontEvent;
typedef std::string s;

class FontSimulator
{

private:
    s fontName;
    s fontSize;
    s fontStyle;
public:
    FontSimulator();
    FontSimulator(s name, s size, s style);
    ~FontSimulator();

    FontEvent OnDraw;

    void DrawText();

    // the setting methods
    void SetFontName(s n) {fontName = n;}
    void SetFontSize(s si) {fontSize = si;}
    void SetFontStyle(s st) {fontStyle = st;}

    // the getting methods
    s GetFontName() {return fontName;}
    s GetFontSize() {return fontSize;}
    s GetFontStyle() {return fontStyle;}
};
#endif
}

そのソースファイル、.cpp

#include "FontSimulator.h"

using namespace eventhandling;

FontSimulator::FontSimulator() : fontName("Default Name"), fontSize ("Default Size"), fontStyle("Default Style")
{
}

FontSimulator::FontSimulator(s fName, s fSize, s fStyle) : fontName(fName), fontSize(fSize), fontStyle(fStyle)
{
}

FontSimulator::~FontSimulator()
{
delete this;
}

void FontSimulator::DrawText()
{
std::cout << "Initialization of font done!" << std::endl << std::endl;
std::cout << fontName << std::endl;
std::cout << fontSize << std::endl;
std::cout << fontStyle << std::endl << std::endl;

for (int i = 0; i < OnDraw.Size(); ++i)
{
    OnDraw.Invoke(i);
    std::cout << "the #" << i + 1 << " responder method called!" << std::endl << std::endl;
    std::cout << fontName << std::endl;
    std::cout << fontSize << std::endl;
    std::cout << fontStyle << std::endl << std::endl;
}
for (int j = 0; j < OnDraw.Size(); j++)
{
    //OnDraw.Remove(j);
    OnDraw -= j;
}

        std::cout << "The finishing font work after all the event handler are called!" << std::endl <<std::endl;
   }

フォントクラスのイベントを処理する抽象基本クラス…</p>

#include "BasicEventHandler.h"

namespace eventhandling
{
#ifndef __IFONT_CLIENT__
#define __IFONT_CLIENT__

class IFontClient
{
public:
    IFontClient(){};
    ~IFontClient(){delete this;}
    virtual void SetupFont() = 0;
};

typedef EventHandler<IFontClient> FontEventHandler;

#endif
}

最初にIFontClient…ヘッダーファイルから派生したクラス

#include "BasicEventHandler.h"
#include "BasicEvent.h"
#include "FontSimulator.h"
#include "IFontClient.h"

namespace eventhandling
{
#ifndef __CONTROL_SIMULATOR__
#define __CONTROL_SIMULATOR__

class ControlSimulator : public IFontClient
{
protected:
    std::string caption;
    FontSimulator* font;

public:
    ControlSimulator();
    ControlSimulator(std::string theCaption, FontSimulator* theFont);
    ~ControlSimulator();

    virtual void Draw();
    virtual void SetupFont();

    void SetCaption(std::string c) {caption = c;}
    std::string GetCaption() {return caption;}
};

#endif
}

そのソースファイル.cpp

#include "ControlSimulator.h"

namespace eventhandling
{
ControlSimulator::ControlSimulator() : caption("Default Caption"), font(new FontSimulator())
{
}

ControlSimulator::ControlSimulator(std::string c, FontSimulator* f) : caption(c), font(f)
{
}

ControlSimulator::~ControlSimulator()
{
    delete this;
}

void ControlSimulator::Draw()
{
    std::cout << "Drawing " << caption << " is done!" << std::endl << std::endl;
}

void ControlSimulator::SetupFont()
{
    std::string costumProperty = caption;
    font->SetFontName(costumProperty.append(", Costumized Font Name"));

    costumProperty = caption;
    font->SetFontSize(costumProperty.append(", Costumized Font Size"));

    costumProperty = caption;
    font->SetFontStyle(costumProperty.append(", Costumized Font Style"));
}
}

アプリをテストするためのメインエントリ

#include "ControlSimulator.h"

using namespace eventhandling;

int main(int argc, char** argv)
{
char c;

FontSimulator* font = new FontSimulator();
ControlSimulator* control1 = new ControlSimulator("Control one", font);
ControlSimulator* control2 = new ControlSimulator("Control two", font);

control1->Draw();
control2->Draw();

font->OnDraw += new FontEventHandler(control1, &ControlSimulator::SetupFont);
font->OnDraw += new FontEventHandler(control2, &ControlSimulator::SetupFont);

font->DrawText();

std::cout << "Enter any character to exit!" << std::endl;
std::cin >> c;

return 0;
}
于 2012-09-01T15:10:39.560 に答える