嵌套多态模板的C ++动态转换

时间:2018-11-28 10:33:58

标签: c++ polymorphism message-queue dynamic-cast

我正在使用提供消息的PolyM message queue

class Msg

以及带有模板有效负载的消息

template<typename PayloadType> class DataMsg: public Msg

这一直有效,直到我将DataMsg模板嵌套在另一个DataMsg中,就像这样...

DataMsg<DataMsg<int>>

并尝试提取嵌套的DataMsg,以将其传递给进一步处理。为此,我将Msg基本类型转换为:

function(Msg &base) {
    auto &msg = dynamic_cast<DataMsg<Msg>&>(base).getPayload();
}

此转换因错误的转换异常而失败。相反,使用static_cast似乎没有任何副作用。

从多态的观点来看,我的方法没有发现任何问题。由于动态类型转换适用于非嵌套类型,它也应该适用于嵌套类型吗?

我已经在PolyM GitHub issues page上问了这个问题,但是并没有得到正确的解释,说明强制转换失败的原因。

这是一个显示问题的简约示例:

#include <memory>
#include <iostream>

using namespace std;

class Msg {
  public:
     virtual ~Msg() {}
};

template<typename PayloadType>
class DataMsg: public Msg {
  public:
     virtual ~DataMsg() {}

     PayloadType& getPayload() const
     {
       return *pl_;
     }

  private:
    PayloadType* pl_;
};

static void getInnerMsg(Msg &msgMsg) { 
    try { 
        auto &msg = dynamic_cast<DataMsg<Msg>&>(msgMsg).getPayload();
        std::cout << "cast OK" << endl;
    } catch ( std::bad_cast& bc ) {      
        std::cerr << "bad_cast caught: " << bc.what() << endl;
    }
}

和我的测试用例:

int main(int argc, char *argv[])
{    
     Msg                     msg1;
     DataMsg<int>            msg2;
     DataMsg<Msg>            msg3;
     DataMsg<DataMsg<int>>   msg4;

    cout << "expect bad cast (no nested message)" << endl;
    getInnerMsg(msg1);
    cout << "-------------" << endl;
    cout << "expect bad cast (no nested message)" << endl;
    getInnerMsg(msg2);
    cout << "-------------" << endl;
    cout << "expect successful cast (nested message base type)" << endl;
    getInnerMsg(msg3);
    cout << "-------------" << endl;
    cout << "expect successful cast (nested message child type)" << endl;
    getInnerMsg(msg4);

    return 0;
}

运行“ g ++ test.cpp -o test.x && ./test.x”。 GitHub问题包含一个更完整的用法示例。

3 个答案:

答案 0 :(得分:0)

(简短地)在github问题答复中进行了解释:

function(Msg &base) {
    auto &msg = dynamic_cast<DataMsg<Msg>&>(base).getPayload();
}

您在这里尝试从Msg投射到DataMsg<Msg>。但是,您指示base的动态类型为DataMsg<DataMsg<int>>DataMsg<Msg>继承自MsgDataMsg<DataMsg<int>>继承自Msg,但它们之间是无关的(无论模板参数之间的关系如何)。

更笼统地说,MyClass<Derived>并不继承自MyClass<Base>-(无论两者是否都源自同一基类),因此动态地从一个或另一个强制转换另一个是非法的(通过/从是否有共同基础。

答案 1 :(得分:0)

  

从多态的观点来看,我的方法没有发现任何问题。由于动态类型转换适用于非嵌套类型,它也应该适用于嵌套类型吗?

关于模板,即使A<B>A<C>是相关的,BC也是不同的不相关类型。使用通过static_cast获得的引用会导致未定义的行为。为了说明这一点,如果我们要手写您得到的类层次结构,它将类似于此

struct base { ~virtual base() = default; };

struct foo : base {
    base *p;
};

struct bar : base {
    foo *p;
};

在上面的示例中,如果对象的动态类型为bar,则不能将其强制转换为foo&,这些类型彼此之间不在继承链中,并且动态转换将会失败。但是从static_cast(指base&)到bar的{​​{1}}将会成功。没有运行时检查,编译器会随便告诉您有关类型的信息,但您并没有说出真相。如果使用该引用,则会发生未定义的行为。可悲的是,“工作”的出现是未定义行为的有效体现。

答案 2 :(得分:0)

  

这一直有效,直到我将DataMsg模板嵌套在另一个DataMsg中...尝试提取嵌套的DataMsg

到目前为止,您的逻辑对于拆开嵌套邮件具有直观的意义。

您有一条带有通用标头的通用消息,该标头包含某种特定类型的动态具体消息,提取特定的消息有效负载,其中包含另一种特定类型的其他消息,等等。

一切都很好。

您的概念性问题是您正在处理嵌套模板参数(例如嵌套消息对象),而它们根本不一样。

template<typename PayloadType> class DataMsg: public Msg
    // ...
  private:
    PayloadType* pl_;
};

表示DataMsg模板 is-a Msg的每个实例,并且具有指向所包含有效负载的指针。

现在,DataMsg的每个实例都是新的类型DataMsg<X>,即使DataMsg<Y>X相关,它也与任何其他实例Y无关(除了它们均来自Msg)。所以:

DataMsg<DataMsg<int>>

is-a Msg has-a 指向DataMsg<int>的指针(本身是 is-a Msg),而

DataMsg<Msg>

也是 is-a Msg has-a 指向Msg的指针,但仍然是与{{ 1}}(除非具有通用基数),即使有效载荷指针类型是可转换的。

因此,关于消息布局的想法很好,但是您没有在类型系统中正确地对其建模。如果要执行此操作,则可以显式地专门处理嵌套消息:

DataMsg<DataMsg<int>>

现在using NestedMsg = DataMsg<Msg>; template<typename NestedPayloadType> class DataMsg<DataMsg<NestedPayloadType>>: public NestedMsg { public: NestedPayloadType const & getDerivedPayload() const { // convert nested Msg payload to derived DataMsg return dynamic_cast<DataMsg<NestedPayloadType> const &>(this->getPayload()); } }; 确实是 a DataMsg<DataMsg<int>>。这样,它继承了DataMsg<Msg>,并且您仍然可以通过调用`getDerivedPayload()获取负载的派生类型(Msg const& DataMsg<Msg>::getPayload() const)。

您的其他DataMsg<int>方法也应返回const引用BTW。