我写了很多处理消息协议的代码。消息协议通常具有通用消息帧,可以从串行端口或套接字反序列化;该帧包含消息类型,并且必须根据消息类型处理消息有效负载。
通常我会使用访问器方法编写一组多态类,并使用构造函数来引用消息帧。
虽然不是基于对消息帧的引用构造一个访问器类,但我可以直接从消息帧中派生出访问者类,然后从消息帧中重新解释为直接到适当的访问者类。这使代码更简洁,并节省了一些字节和处理器周期。
请参阅下面的(非常人为和精简)示例。显然,对于生产代码,这将需要被适当地封装,转换成为派生类的成员,更好地分离所关注的问题,并添加一些验证。为了整理一个简洁的例子,这一切都被删除了。
#include <iostream>
#include <cstring>
#include <vector>
struct GenericMessage
{
GenericMessage(const char* body):body_(body, body+strlen(body)){}
std::vector<char> body_;
};
struct MessageType1:public GenericMessage
{
int GetFoo()const
{
return body_[2];
}
int GetBar()const
{
return body_[3];
}
};
int main()
{
GenericMessage myGenericMessage("1234");
MessageType1* myMgessageType1 = reinterpret_cast<MessageType1*>(&myGenericMessage);
std::cout << "Foo:" << myMgessageType1->GetFoo() << std::endl;
std::cout << "Bar:" << myMgessageType1->GetBar() << std::endl;
return 0;
}
我从未在任何地方看到过这种情况。如果派生没有其他数据成员,那么从基础转换到以这种方式派生是否有任何缺点?
答案 0 :(得分:4)
这就是为什么我不会使用这种技术:
违反了标准,导致行为未定义。这种情况可能几乎一直在起作用,但你不能在将来排除问题。编译器have been seen在优化中使用未定义的行为,这对于毫无戒心的程序员来说是非常不利的。你无法预测何时以及在什么情况下会发生这种情况。
您不能保证您和队友都不会将某些数据成员添加到派生类型。您的类层次结构将会增长,并且会随着时间添加更多代码;在某些时候,对于您或其他程序员而言,向派生类型添加无辜数据成员(即使是暂时的,可能是出于某些调试目的)也可能不会显而易见。
有一些清晰合法的替代方案,例如使用基于引用的包装器:
#include <iostream>
struct Elem
{ };
struct ElemWrapper
{
Elem &elem_;
ElemWrapper(Elem &elem) : elem_(elem)
{ }
};
struct ElemWrapper1 : ElemWrapper
{
using ElemWrapper::ElemWrapper;
void foo()
{ std::cout << "foo1" << std::endl; }
};
struct ElemWrapper2 : ElemWrapper
{
using ElemWrapper::ElemWrapper;
void foo()
{ std::cout << "foo2" << std::endl; }
};
int main()
{
Elem e;
ElemWrapper1(e).foo();
return 0;
}
答案 1 :(得分:1)
不,你不能!
它可能在你的情况下工作,但它是不可取的,因为(快速解释)派生类可能有更多的成员或虚函数,这些都不会从基础上获得。
最简单的解决方案是保留继承方案(这很好),但使用工厂来实例化正确的消息类型。示例:
struct GenericMessage* create_message(const char* body) {
int msg_type = body[5]; // I don't know where type is coded, this is an example
switch(msg_type) {
case 1:
return new MessageType1(body);
break;
// etc.
您可以稍后安全地dynamic_cast
。
请注意,您可以将工厂放在任何地方,例如GenericMessage类本身,即。
GenericMessage myGenericMessage("1234");
MessageType1* myMgessageType1 = myGenericMessage.get_specialized_message();
或者,您也可以从基础消息构建专门的消息,但最后也是如此:
GenericMessage myGenericMessage("1234");
MessageType1* myMgessageType1 = new MessageType1( myGenericMessage );
答案 2 :(得分:1)
如果添加以下测试,它在许多应用程序中就足够了:
static_assert(
sizeof(MessageType1) == sizeof(GenericMessage),
"Cannot overlay MessageType1 upon GenericMessage." );
没有编译器优化会改变派生类型的基类型slice的布局,所以这通常是足够安全的。
另外,使用 static_cast
。 reinterpret_cast
用于比这更反常的事情。
...好吧,是的,是的,当所有以下条件都为真时,这可能会失败:
GenericMessage
在末尾有填充 。MessageType1
happens to lay inside that padding 中的成员(后来添加)。MessageType1
,该代码路径读取该填充区在写入之前。< /li>
所以权衡权宜与稳健,然后做你认为最好的事情。您不是第一个使用这种模式的人,这也不是禁忌,尽管这里的其他答案令人心痛——尽管他们肯定是正确的,因为它具有特殊的危害。