【问题标题】:passing a derived class object to constructor of template class将派生类对象传递给模板类的构造函数
【发布时间】:2012-08-31 13:29:56
【问题描述】:

大家好!

我的问题如下:

我有一个模板类,它存储指向其他类的方法的指针(当然,模板类最初不知道要传递给它的类的类型)。 我让模板类的构造函数获取一个指向传入类的指针,另一个参数是我要存储的传入类的方法的地址,如下:

    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 定义,只创建模板类对象,每个对象都具有不同的派生类类型。不过,以上提议是我的本意。

我正在使用 Visual Studio IDE

提前感谢您的关注和帮助。

【问题讨论】:

  • 在您的DerivedClassDoSomething 不是虚拟的是否正常?
  • 你的前两个 sn-ps 不是有效的 C++。很难说你到底在做什么......
  • @J.N.不一定,可以是虚拟的。
  • 是的,我发现它非常令人困惑。任何我发现这样做的人都会反手。明确!
  • 您尝试使用TemplateClass 实现的目标正是std::function 的目标。我强烈推荐使用它...

标签: c++


【解决方案1】:

问题是您不能将指向方法的指针从派生类型的指向方法的指针转换为基类型的指向方法的指针,因为指向方法的指针是逆变与对象类型。

考虑:

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

如果你被允许写作

pmethod = &Derived::doSomethingElse;

那么您可以使用pmethodinstance 类型的Base 上调用Derived::doSomethingElse

Liskov's substitution principle 下,Derived 对象(引用)是 Base 对象(引用),因为您可以对 Base 执行的任何操作都可以对 Derived 执行,但是指向方法的指针是 Derived - 不是指向基方法的指针;事实上,它是相反的(指向方法的指针是指向方法的指针),这就是为什么我们说指向方法的指针是逆变的:

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

在您的情况下,最好的选择可能是编写模板构造函数并使用类型擦除来隐藏 Derived 方法指针的类型;在下面

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

构造函数类型中的两个Ts可以相互抵消,给出函数签名void ()。你可以通过std::function&lt;void ()&gt; 成员来做到这一点:

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

【讨论】:

  • 感谢您的解释。我试过你的方法,现在编译成功了。但是,在运行时,当应用程序到达执行地址存储在模板类实例中的方法的阶段时,它会出现访问冲突错误并停在那里!对此有任何想法。
  • 错误是:####.exe 中 0xcdcdcdcd 处未处理的异常:0xC0000005:访问冲突。 (其中#### 是文件名)
  • @user1638717 你确定实例没有被删除吗?用一个简短的例子来试试(例如,不使用你的 TemplateClass),看看它是否有效。
  • @user1638717 0xcdcdcdcd 看起来像 VS 分配给未初始化内存的内存模式。见softwareverify.com/memory-bit-patterns.php
  • 我会尝试做点什么。非常感谢您的宝贵建议。
【解决方案2】:

这是什么意思:TemplateClass(MyClass* class, &amp;MyClass::SomeMethod); 这不是有效的 C++ 代码。为什么不坚持使用 C++ 标准库?如果您需要存储可调用对象,请使用从 lambda 构造的 std::function。编译器和库会处理所有必要的转换...

【讨论】:

  • 对不起,这是一个打字错误。这是我在其他地方调用构造函数的方式,而不是声明或定义。我已经在我的问题中纠正了这个错误。谢谢。
【解决方案3】:

嗯,我从事的项目是一个试验,以建立一种机制来处理 C++ 中的事件,类似于 - 或多或少类似于 C# 的事件和事件处理程序方式。 所以,在网上和一些文本中研究并找出合适的方法后,我写了两个模板类: 一种是为成员方法存储一个指针,并将其称为“EventHandler”。 另一种是存储事件处理程序的映射并在需要时调用它们;这是“事件”类。 然后我写了两个“普通”类:一个是事件触发类,另一个是响应类或监听类。 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 对象时摆脱显式的 '<.....>' 模板语法,这显然是 C# 中的情况,因此,我将其简化如下:

    typedef EventHandler<MyClass> SomeEventFired_EventHandler;

这样当我需要创建这个模板的新对象时,我只需要写:

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

当然,后面的完整代码示例会更清楚! 我的问题来了,我希望能够传递 MyClass 的派生类的对象。问题是如上所示的 EventHandler 的模板不接受这样的派生对象,因为它只期望基类的对象,编译器抱怨它无法从派生类转换为基类。 当他向我展示了使 EventHandler 类的构造函数模板化的正确方法时,ecatmur 提供了宝贵的帮助。 这样,当我使用 MyClass 作为基类键入定义的 SomeEventFired_EventHandler 时,我就能够将任何对象及其方法传递给它的构造函数,只要该对象来自从 MyClass 派生的类。这是我实现 EventHandler 多态特性的最终目标。 我想要这个功能,因为如果你检查 C# 中的 EventHandlers,你可以看到 System::EventHandler 是多态的,它接受来自类的不同对象,这些对象基本上是从类 Object 派生的,我猜。 所以,这里是完整的例子,带有基于 ecatmur 解决方案的整改 EventHandler 类,供大家查看,希望对您有所帮助。 最终,您可以从 BaseEventHandler 类派生,以便派生的 EventHandler 可以存储具有不同返回类型和不同参数参数的方法,因为这里显示的基本方法接受返回 void 和取 void 的方法(我相信您可以这样做更改 std::function 的声明以使其接受其他类型的方法,例如,

std::function<int(int)> 

,以此类推)。

事件类同上……

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

事件触发类头文件……

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

处理字体类事件的抽象基类……

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-07
    • 2013-05-11
    • 2014-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多