带有嵌套多态模板的c++动态转换



我使用的是PolyM消息队列,它提供消息

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问题页面上问过这个问题,但没有得到演员阵容失败的正确解释。

这是一个显示问题的极简主义例子:

#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问题包含一个更完整的用法示例。

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

当涉及到模板时,A<B>A<C>是不同的不相关类型,即使BC是相关的。使用static_cast获得的引用会导致未定义的行为。举例来说,如果我们手工编写生成的类层次结构,它将类似于这个

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

在上面的示例中,如果对象的动态类型是bar,则不能将其强制转换为foo&,因为这些类型彼此不在继承链中,动态强制转换将失败。但是从base&(指bar(到foo&static_cast将成功。没有运行时检查,编译器会根据您对类型的判断,但您没有告诉它真相。如果使用该引用,则会出现未定义的行为。可悲的是,"工作"的出现是未定义行为的有效表现。

如github问题回复中(简要(所述:

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

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

更一般地说,MyClass<Derived>不是从MyClass<Base>-继承的(无论两者是否派生自同一基类(,因此从一个基类到另一个基类的动态强制转换是非法的(是否通过公共基类(。

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

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

您有一个带有通用标头的通用消息,该标头包含某个特定类型的动态具体消息,提取特定消息有效载荷,该有效载荷包含另一特定类型的另一消息,等等。

一切都很好。

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

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

意味着DataMsg模板的每个实例化都是一个Msg,并且它有一个指向所包含有效载荷的指针。

现在,DataMsg的每个实例化都是一个新的类型DataMsg<X>,它与任何其他实例化DataMsg<Y>无关,即使XY是相关的(除了它们都源自Msg(。因此:

DataMsg<DataMsg<int>>

is-aMsg有一个指向DataMsg<int>指针(其本身is-aMsg(,而

DataMsg<Msg>

也是一个Msg,并且有一个指向Msg指针,但仍然是一个与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());
}
};

现在DataMsg<DataMsg<int>>实际上是一个DataMsg<Msg>。因此,它继承了Msg const& DataMsg<Msg>::getPayload() const,并且您仍然可以通过调用`getDerivedPayload((来获得有效负载的派生类型(DataMsg<int>(。

您的另一个getPayload方法也应该返回const-ref,BTW.

最新更新