简体   繁体   English

给定抽象基础 class X,如何创建另一个模板 class D<t> 其中 T 是从 X 派生的 class 的类型?</t>

[英]given abstract base class X, how to create another template class D<T> where T is the type of the class deriving from X?

I want to be able to accept a Message& object which references either a Message1 or Message2 class.我希望能够接受引用Message1Message2 class 的Message& object。 I want to be able to create a MessageWithData<Message1> or MessageWithData<Message2> based on the underlying type of the Message& object.我希望能够根据Message& object 的基础类型创建MessageWithData<Message1>MessageWithData<Message2> For example, see below:例如,见下文:

class Message {};
class Message1 : public Message {};
class Message2 : public Message {};

template<typename Message1or2>
class MessageWithData : public Message1or2 { public: int x, y; }

class Handler()
{
public:
  void process(const Message& message, int x, int y)
  {
    // create object messageWithData whose type is 
    // either a MessageWithData<Message1> or a MessageWithData<Message2> 
    // based on message's type.. how do I do this?
    //
    messageWithData.dispatch(...)
  }
};

The messageWithData class essentially contains methods inherited from Message which allow it to be dynamically double dispatched back to the handler based on its type. messageWithData class 本质上包含从 Message 继承的方法,这些方法允许根据其类型动态地将其双重分派回处理程序。 My best solution so far has been to keep the data separate from the message type, and pass it all the way through the dynamic dispatch chain, but I was hoping to come closer to the true idiom of dynamic double dispatch wherein the message type contains the variable data.到目前为止,我最好的解决方案是将数据与消息类型分开,并一直通过动态调度链传递它,但我希望更接近动态双重调度的真正习惯,其中消息类型包含可变数据。

(The method I'm more or less following is from http://jogear.net/dynamic-double-dispatch-and-templates ) (我或多或少遵循的方法来自http://jogear.net/dynamic-double-dispatch-and-templates

You're trying to mix runtime and compile-time concepts, namely (runtime-)polymorphism and templates.您正在尝试混合运行时和编译时概念,即(运行时)多态性和模板。 Sorry, but that is not possible.对不起,但那是不可能的。

Templates operate on types at compile time, also called static types .模板在编译时对类型进行操作,也称为static types The static type of message is Message , while its dynamic type might be either Message1 or Message2 . static 类型的messageMessage ,而其动态类型可能是Message1Message2 Templates don't know anything about dynamic types and they can't operate on them.模板对动态类型一无所知,也无法对其进行操作。 Go with either runtime polymorphism or compile-time polymorphism, sometimes also called static polymorphism. Go 具有运行时多态性编译时多态性,有时也称为 static 多态性。

The runtime polymorphism approach is the visitor pattern, with double dispatch.运行时多态方法是访问者模式,具有双重调度。 Here is an example of compile-time polymorphism, using the CRTP idiom :下面是一个使用CRTP 习惯用法的编译时多态性示例:

template<class TDerived>
class Message{};

class Message1 : public Message<Message1>{};
class Message2 : public Message<Message2>{};

template<class TMessage>
class MessageWithData : public TMessage { public: int x, y; };

class Handler{
public:
  template<class TMessage>
  void process(Message<TMessage> const& m, int x, int y){
    MessageWithData<TMessage> mwd;
    mwd.x = 42;
    mwd.y = 1337;
  }
};

You have你有

void process(const Message& message, int x, int y)
{
  // HERE
  messageWithData.dispatch(...)
}

At HERE, you want to create either a MessageWithData<Message1> or a MessageWithData<Message2> , depending on whether message is an instance of Message1 or Message1 .在这里,您想要创建MessageWithData<Message1>MessageWithData<Message2> ,具体取决于messageMessage1还是Message1的实例。

But you cannot do that, because the class template MessageWithData<T> needs to know at compile time what T should be, but that type is not available at that point in the code until runtime by dispatching into message .但是您不能这样做,因为 class 模板 MessageWithData MessageWithData<T>需要在编译时知道T应该是什么,但是在运行时通过调度到message之前,该类型在代码中不可用。

As Xeo says, you probably shouldn't do this in this particular case - better design alternatives exist.正如 Xeo 所说,在这种特殊情况下您可能不应该这样做 - 存在更好的设计替代方案。 That said, you can do it with RTTI, but it's generally frowned upon because your process() becomes a centralised maintenance point that needs to be updated as new derived classes are added.也就是说,您可以使用 RTTI 来做到这一点,但它通常不受欢迎,因为您的process()成为一个集中维护点,需要在添加新的派生类时进行更新。 That's easily overlooked and prone to run-time errors.这很容易被忽视并且容易出现运行时错误。

If you must persue this for some reason, then at least generalise the facility so a single function uses RTTI-based runtime type determination to invoke arbitrary behaviour, as in:如果您出于某种原因必须坚持这一点,那么至少要概括该设施,以便单个 function 使用基于 RTTI 的运行时类型确定来调用任意行为,如下所示:

#include <iostream>
#include <stdexcept>

struct Base
{
    virtual ~Base() { }

    template <class Op>
    void for_rt_type(Op& op);
};

struct Derived1 : Base
{
    void f() { std::cout << "Derived1::f()\n"; }
};

struct Derived2 : Base
{
    void f() { std::cout << "Derived2::f()\n"; }
};

template <class Op>
void Base::for_rt_type(Op& op)
{
    if (Derived1* p = dynamic_cast<Derived1*>(this))
        op(p);
    else if (Derived2* p = dynamic_cast<Derived2*>(this))
        op(p);
    else
        throw std::runtime_error("unmatched dynamic type");
}

struct Op
{
    template <typename T>
    void operator()(T* p)
    {
        p->f();
    }
};

int main()
{
    Derived1 d1;
    Derived2 d2;
    Base* p1 = &d1;
    Base* p2 = &d2;
    Op op;
    p1->for_rt_type(op);
    p2->for_rt_type(op);
}

In the code above, you can substitute your own Op and have the same runtime-to-compiletime handover take place.在上面的代码中,您可以替换您自己的 Op 并进行相同的运行时到编译时切换。 It may or may not help to think of this as a factory method in reverse:-}.将其视为反向工厂方法可能有帮助,也可能无济于事:-}。

As discussed, for_rt_type has to be updated for each derived type: particularly painful if one team "owns" the base class and other teams write derived classes.如前所述,必须为每个派生类型更新for_rt_type :如果一个团队“拥有”基础 class 而其他团队编写派生类,则尤其痛苦。 As with a lot of slightly hacky things, it's more practical and maintainable in support of private implementation rather than as an API feature of a low-level enterprise library.与许多稍微有点 hacky 的东西一样,它在支持私有实现方面更实用和可维护,而不是作为低级企业库的 API 功能。 Wanting to use this is still typically a sign of bad design elsewhere, but not always: occasionally there are algorithms ( Op s) that benefit enormously:想要使用它通常仍然是其他地方糟糕设计的标志,但并非总是如此:偶尔会有算法( Op )受益匪浅:

  • compile-time optimisations, dead code removal etc.编译时优化,死代码删除等。
  • derived types only need same semantics, but details can vary派生类型只需要相同的语义,但细节可能会有所不同
    • eg Derived1::value_type is int , Derived2::value_type is double - allows algorithms for each to be efficient and use appropriate rounding etc.. Similarly for different container types where only a shared API is exercised.例如Derived1::value_typeintDerived2::value_typedouble - 允许每个算法高效并使用适当的舍入等。同样对于不同的容器类型,只使用共享的 API。
  • you can use template metaprogramming, SFINAE etc. to customise the behaviours in a derived-type specific way您可以使用模板元编程、SFINAE 等以特定于派生类型的方式自定义行为

Personally, I think knowledge of and ability to apply this technique (however rarely) is an important part of mastering polymorphism.就个人而言,我认为应用这种技术的知识和能力(但很少)是掌握多态性的重要组成部分。

As has been mentioned, it is not possible to build your template as is.如前所述,不可能按原样构建您的模板。

I do not see any issue with passing additional parameters, though I would perhaps pack them into a single structure, for ease of manipulation.我认为传递附加参数没有任何问题,尽管我可能会将它们打包成一个结构,以便于操作。

Certainly I find it more idiomatic to use a supplementary Data parameter, rather than extending a class hierarchy to shoehorn all this into a pattern .当然,我发现使用补充Data参数更惯用,而不是扩展 class 层次结构将所有这些硬塞到一个模式中。

It is an anti-pattern to try to make a design fit a pattern.试图使设计适合模式是一种反模式。 The proper way is to adapt the pattern so that it fits the design.正确的方法是调整模式,使其适合设计。

That being said...话虽如此...


There are several alternatives to your solution.您的解决方案有多种选择。 Inheritance seems weird, but without the whole design at hand it may be your best bet. Inheritance 看起来很奇怪,但如果没有整个设计,它可能是你最好的选择。

It has been mentioned already that you cannot freely mix compile-time and run-time polymorphisms.已经提到过,您不能随意混合编译时和运行时多态性。 I usually use Shims to circumvent the issue:我通常使用 Shims 来规避这个问题:

class Message {};
template <typename T> class MessageShim<T>: public Message {};
class Message1: public MessageShim<Message1> {};

The scheme is simple and allow you to benefit from the best of both worlds:该方案很简单,可让您从两全其美中受益:

  • Message being non-template mean that you can apply traditional OO strategies Message非模板意味着您可以应用传统的OO策略
  • MessageShim<T> being template mean that you can apply traditional Generic Programming strategies MessageShim<T>是模板意味着您可以应用传统的通用编程策略

Once done, you should be able to get what you want, for better or worse.一旦完成,你应该能够得到你想要的,无论好坏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM