我使用的是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>
是不同的不相关类型,即使B
和C
是相关的。使用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>
无关,即使X
和Y
是相关的(除了它们都源自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.