c ++带有嵌套多态模板的动态转换
c++ Dynamic cast with nested polymorphic template
我正在使用 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 问题包含更完整的用法示例。
正如 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>
-(无论两者是否派生自相同的基础 class),因此动态转换一个 from/to另一个是非法的(through/from 是否有共同基础)。
From a polymorphic view point I can't see anything wrong with my approach. As dynamic casting works for the non-nested type, it should work for the nested one too?
就模板而言,A<B>
和 A<C>
是截然不同的不相关类型,即使 B
和 C
是相关的。使用通过 static_cast
获得的引用会导致未定义的行为。举例来说,如果我们要手写您得到的 class 层次结构,它将类似于此
struct base { ~virtual base() = default; };
struct foo : base {
base *p;
};
struct bar : base {
foo *p;
};
在上面的示例中,如果对象的动态类型是bar
,则不能转换为foo&
,这些类型之间不在继承链中,并且动态转换将失败。但是一个static_cast
从base&
(指一个bar
)到foo&
都会成功。没有 运行 时间检查,编译器相信你所说的类型,但你没有说实话。如果您使用该引用,则会出现未定义的行为。遗憾的是 "working" 的出现是未定义行为的有效表现。
This works until I nest the DataMsg template inside another DataMsg ... try to extract the nested DataMsg
到目前为止,您的逻辑对于解包嵌套消息具有直观意义。
您有一个通用消息,其中包含一个包含某种特定类型的动态具体消息的通用 header,提取特定消息负载,其中包含另一条特定类型的消息,等等。
一切都很好。
您的概念性问题是您将嵌套模板参数视为嵌套消息 objects,它们根本不一样。
template<typename PayloadType> class DataMsg: public Msg
// ...
private:
PayloadType* pl_;
};
表示 DataMsg
模板的每个实例化 is-a Msg
和它 has-a 指向包含的有效载荷的指针。
现在,DataMsg
的每个实例化都是一个新类型 DataMsg<X>
,它与任何其他实例化 DataMsg<Y>
无关,即使 X
和 Y
是相关的(除了它们都派生自 Msg
)。所以:
DataMsg<DataMsg<int>>
is-a Msg
和 has-a 指向 DataMsg<int>
(它本身 is-a Msg
), 而
DataMsg<Msg>
还有is-aMsg
,以及has-a指向[=15=的指针],但它仍然是一种与 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>>
真的is-aDataMsg<Msg>
。因此它继承了 Msg const& DataMsg<Msg>::getPayload() const
,你仍然可以通过调用 `getDerivedPayload().
来获取 payload 的派生类型 (DataMsg<int>
)
你的其他 getPayload
方法也应该 return 一个常量引用,顺便说一句。
我正在使用 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 问题包含更完整的用法示例。
正如 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>
-(无论两者是否派生自相同的基础 class),因此动态转换一个 from/to另一个是非法的(through/from 是否有共同基础)。
From a polymorphic view point I can't see anything wrong with my approach. As dynamic casting works for the non-nested type, it should work for the nested one too?
就模板而言,A<B>
和 A<C>
是截然不同的不相关类型,即使 B
和 C
是相关的。使用通过 static_cast
获得的引用会导致未定义的行为。举例来说,如果我们要手写您得到的 class 层次结构,它将类似于此
struct base { ~virtual base() = default; };
struct foo : base {
base *p;
};
struct bar : base {
foo *p;
};
在上面的示例中,如果对象的动态类型是bar
,则不能转换为foo&
,这些类型之间不在继承链中,并且动态转换将失败。但是一个static_cast
从base&
(指一个bar
)到foo&
都会成功。没有 运行 时间检查,编译器相信你所说的类型,但你没有说实话。如果您使用该引用,则会出现未定义的行为。遗憾的是 "working" 的出现是未定义行为的有效表现。
This works until I nest the DataMsg template inside another DataMsg ... try to extract the nested DataMsg
到目前为止,您的逻辑对于解包嵌套消息具有直观意义。
您有一个通用消息,其中包含一个包含某种特定类型的动态具体消息的通用 header,提取特定消息负载,其中包含另一条特定类型的消息,等等。
一切都很好。
您的概念性问题是您将嵌套模板参数视为嵌套消息 objects,它们根本不一样。
template<typename PayloadType> class DataMsg: public Msg
// ...
private:
PayloadType* pl_;
};
表示 DataMsg
模板的每个实例化 is-a Msg
和它 has-a 指向包含的有效载荷的指针。
现在,DataMsg
的每个实例化都是一个新类型 DataMsg<X>
,它与任何其他实例化 DataMsg<Y>
无关,即使 X
和 Y
是相关的(除了它们都派生自 Msg
)。所以:
DataMsg<DataMsg<int>>
is-a Msg
和 has-a 指向 DataMsg<int>
(它本身 is-a Msg
), 而
DataMsg<Msg>
还有is-aMsg
,以及has-a指向[=15=的指针],但它仍然是一种与 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>>
真的is-aDataMsg<Msg>
。因此它继承了 Msg const& DataMsg<Msg>::getPayload() const
,你仍然可以通过调用 `getDerivedPayload().
DataMsg<int>
)
你的其他 getPayload
方法也应该 return 一个常量引用,顺便说一句。