【问题标题】:c++ Dynamic cast with nested polymorphic template带有嵌套多态模板的 c++ 动态转换
【发布时间】:2019-04-30 05:44:11
【问题描述】:

我正在使用提供消息的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 issue 包含更完整的使用示例。

【问题讨论】:

  • 您为什么希望在案例 4 中成功转换? DataMsg&lt;DataMsg&lt;int&gt;&gt; 没有任何 DataMsg&lt;Msg&gt; 类型的主题。

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


【解决方案1】:

正如(简要)在 github 问题回复中解释的那样:

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

在这里您尝试从Msg 转换为DataMsg&lt;Msg&gt;。但是,您指出base 的动态类型是DataMsg&lt;DataMsg&lt;int&gt;&gt;DataMsg&lt;Msg&gt; 继承自 MsgDataMsg&lt;DataMsg&lt;int&gt;&gt; 继承自 Msg,但它们在其他方面无关(无论模板参数之间的关系如何)。

更一般地说,MyClass&lt;Derived&gt; 不继承自 MyClass&lt;Base&gt; - (无论两者是否都派生自同一个基类),因此从/到另一个的动态转换是非法的(通过/来自公共基类) )。

【讨论】:

    【解决方案2】:

    从多态的角度来看,我看不出我的方法有什么问题。由于动态转换适用于非嵌套类型,它也应该适用于嵌套类型?

    对于模板,A&lt;B&gt;A&lt;C&gt; 是截然不同的不相关类型,即使 BC 是相关的。使用通过static_cast 获得的引用会导致未定义的行为。为了说明,如果我们要手写您生成的类层次结构,它将类似于此

    struct base { ~virtual base() = default; };
    
    struct foo : base {
        base *p;
    };
    
    struct bar : base {
        foo *p;
    };
    

    在上面的示例中,如果对象的动态类型为bar,则不能将其强制转换为foo&amp;,因为这些类型彼此不在继承链中,因此动态强制转换将失败应该。但是从base&amp;(指bar)到foo&amp;static_cast 会成功。没有运行时检查,编译器会告诉你关于类型的信息,但你没有说实话。如果您使用该引用,则会出现未定义的行为。可悲的是,“工作”的出现是未定义行为的有效表现。

    【讨论】:

    • 我认为所有三个答案都是正确的,但是这很简短,准确并且指向我有未定义的行为。
    【解决方案3】:

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

    到目前为止,您的逻辑对于解包嵌套消息具有直观意义。

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

    一切都很好。

    您的概念问题是您将嵌套模板参数视为嵌套消息对象,它们根本不一样。

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

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

    现在,DataMsg 的每个实例化都是一个新类型 DataMsg&lt;X&gt;,它与任何其他实例化 DataMsg&lt;Y&gt; 无关,即使 XY 相关(除了它们都派生自 Msg )。所以:

    DataMsg<DataMsg<int>>
    

    is-a Msghas-a 指向 DataMsg&lt;int&gt; 的指针(它本身 is-a Msg) , 而

    DataMsg<Msg>
    

    is-a Msg,并且 has-a 指针指向 Msg,但仍然是与 DataMsg&lt;DataMsg&lt;int&gt;&gt; 无关的新类型(除了有一个共同的基础),即使负载指针类型是可转换的。

    所以,您对消息布局的想法很好,但是您没有在类型系统中正确地对它们进行建模。如果你想这样做,你可以明确地专门用于嵌套消息:

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

    现在DataMsg&lt;DataMsg&lt;int&gt;&gt; 真的是一个 DataMsg&lt;Msg&gt;。因此它继承了Msg const&amp; DataMsg&lt;Msg&gt;::getPayload() const,您仍然可以通过调用`getDerivedPayload() 获取有效载荷的派生类型(DataMsg&lt;int&gt;)。

    您的其他 getPayload 方法也应该返回一个 const ref,顺便说一句。

    【讨论】:

    • > 如果你想这样做,你可以明确地专门用于嵌套消息:这使得最小的例子运行良好,但是一旦我想使用 getDerivedPayload() 函数,我得到一个编译错误。它也只给了我插入的有效负载,但我最初的方法是提取整个嵌套消息以进行进一步处理。这甚至可能吗? > 您的其他 getPayload 方法也应该返回一个 const ref,顺便说一句。 getPayload() 在 PolyM 中用于获取和设置消息,因此不能为 const。
    • auto &pMsg4 = msg4.getDerivedPayload(); 给了我错误 error: invalid initialization of type of 'const int&' from expression of type 'const DataMsg' ' return dynamic_cast const &>(this->getPayload());
    • 我现在通过使用 pointer-to-msg 作为有效负载而不是维护者github 推荐的 msg 解决了这个问题。我认为这是解决问题的更清洁的方法
    猜你喜欢
    • 1970-01-01
    • 2021-09-21
    • 1970-01-01
    • 2019-02-25
    • 2016-06-24
    • 2012-08-23
    • 2012-11-08
    • 1970-01-01
    • 2020-09-25
    相关资源
    最近更新 更多