几分钟前我正在回答question,然后再向我提出另一个问题:
在我的一个项目中,我做了一些网络消息解析。消息的形式为:
[1 byte message type][2 bytes payload length][x bytes payload]
有效负载的格式和内容由消息类型决定。我有一个基于公共类Message
的类层次结构。
要实例化我的消息,我有一个静态解析方法,根据消息类型字节返回Message*
。类似的东西:
Message* parse(const char* frame)
{
// This is sample code, in real life I obviously check that the buffer
// is not NULL, and the size, and so on.
switch(frame[0])
{
case 0x01:
return new FooMessage();
case 0x02:
return new BarMessage();
}
// Throw an exception here because the mesage type is unknown.
}
我有时需要访问子类的方法。由于我的网络消息处理必须快,我决定避免使用dynamic_cast<>
,并向基础Message
类添加了一个方法,该类提供了消息类型。根据此返回值,我使用static_cast<>
代替正确的子类型。
我之所以这样做,主要是因为我被告知dynamic_cast<>
很慢。但是,我并不确切地知道它真正做了什么以及它有多慢,因此,我的方法可能同样慢(或慢)但更复杂。
你们对这个设计有什么看法?这很常见吗?它真的比使用dynamic_cast<>
快吗?当一个人使用dynamic_cast<>
时欢迎发生什么事情的详细解释!
---编辑---
因为有些人问为什么:
基本上,当我收到一个框架时,我会做两件事:
Message
的子类的相应实例。除了解析部分之外,还有 no 逻辑。Message
,并且根据switch(message->getType())
,我static_cast<>
为正确的类型,并对该消息执行任何操作。答案 0 :(得分:7)
dynamic_cast的实现当然会因编译器而异。
在Visual C ++中,vtable指向包含有关结构的所有RTTI的结构。因此,dynamic_cast涉及解除引用此指针,并根据请求的类型检查“实际”类型,并在它们不兼容时抛出异常(或返回NULL)。它基本上等同于您描述的系统。这不是特别慢。
您的设计听起来有些偏离 - 您有一个工厂方法会忘记对象的真实类型,然后您立即想要忘记这些信息。也许你应该在将类型遗忘到工厂方法时,或者在基类本身的虚拟方法中移动你所做的逻辑。
答案 1 :(得分:4)
“更快”的唯一正确答案是“试试吧。”
答案 2 :(得分:3)
这取决于您管理邮件的方式。当我有switch
根据类型选择消息时,最佳选项是使用static_cast
,因为您知道函数解析器将为您创建正确的类型。
Message* gmsg parse(frame);
switch (gmsg->type) {
case FooMessage_type:
FooMessage* msg=static_cast<FooMessage*)(gmsg);
// ...
break;
case BarMessage_type:
BarMessage* msg=static_cast<BarMessage*)(gmsg);
//...
break;
};
这里使用dynamic_cast
是过度保护。
为什么你需要从一个普通的消息继承所有消息?有什么共性? 我将添加另一个不使用继承的设计
switch (frame::get_msg_type(aframe)) {
case FooMessage_type:
FooMessage msg=parse<FooMessage)(aframe);
// work with msg
break;
case BarMessage_type:
BarMessage msg=parse<BarMessage)(aframe);
//...
break;
};
其中parse将帧解析为MSG,或者在解析失败时抛出异常。
我看到其他答案告诉你使用虚函数。我真的没有看到这个消息的OO设计有任何优势。
答案 3 :(得分:3)
当人们说dynamic_cast很慢时,这只是一个经验法则。 dynamic_cast或多或少会做你正在做的事情。它很慢,因为它涉及几个内存访问。有点像人们说虚拟功能很慢。你正在快速(一个函数调用)并添加一些内存访问。这是一个显着的减速(因为一直到ram和返回可能需要几百个周期),但对于大多数人来说,只要它不经常做(只有非常大的值)并不重要
答案 4 :(得分:2)
A)这听起来非常像过早的优化。
B)如果你的设计需要这么多次调用dynamic_cast&lt;&gt;你担心它,那么你肯定需要看看你的设计并弄清楚它有什么问题。
C)与之前的回答一样,回答是否更快的唯一方法是使用分析器(或等效的)并进行比较。
答案 5 :(得分:1)
你关注速度,但正确性是什么?
根本问题是你确定你不会犯错吗?特别是,您可能想要以这样的方式包装转换方法:
template <class T>
T* convert(Message* message)
{
if (message == 0) return 0;
else return message->getType() == T::Type() ? static_cast<T*>(message) : 0;
}
为了将测试和强制转换嵌入到单个函数中,从而避免了错误,例如:
switch(message->getType())
{
case Foo:
{
//...
// fallthrough
}
case Bar:
{
BarMessage* bar = static_cast<BarMessage*>(message); // got here through `Foo`
}
}
或显而易见的:
if (message->getType() == Foo) static_cast<BarMessage*>(message); // oups
不可否认的是,但也没有多少努力。
另一方面,您还可以通过应用运行时调度来检查您的技术。
virtual
种方法Visitor
等...
答案 6 :(得分:0)
您已经有了一个抽象基类“Message”。使用它作为接口来隐藏FooMessage和BarMessage的实现细节。
我想,这就是你选择这种方法的原因,或者不是吗?
答案 7 :(得分:0)
我知道这篇文章有点陈旧,但我和这个问题的作者有完全相同的问题。
我还需要将一个抽象基类(MessageAbstract
)向下转换为一个或多个具体的消息类,而不依赖于MessageHeader
中的类型字段。由于具体消息的长度和数据可能不同,因此不可能在MessageAbstract
中包含所有内容。
我也使用static_cast
方法,因为我比Opr更熟悉OOP而不是元编程。
在当前项目中使用C ++ 11,我想在这个答案中概述我的解决方案。以下解决方案与Vicente Botet Escriba提供的解决方案非常相似,但使用 Modern C ++ 。
#include <cstdint>
#include <memory>
namespace Example {
enum class MessageTypes : std::uint8_t {
kFooMessage = 0x01,
kBarMessage = 0x02
};
class MessageHeader {
public:
explicit MessageHeader(MessageTypes const kType) : kType_{kType} {
}
MessageTypes type() const noexcept {
return this->kType_;
}
private:
MessageTypes const kType_;
};
class MessageAbstract {
public:
explicit MessageAbstract(MessageHeader const kHeader) : kHeader_{kHeader} {
}
MessageHeader header() const noexcept {
return this->kHeader_;
}
private:
MessageHeader const kHeader_;
};
class FooMessage : public MessageAbstract {
public:
void specific_method_for_class_foo_message() const noexcept {
}
// ...
};
class BarMessage : public MessageAbstract {
public:
void specific_method_for_class_bar_message() const noexcept {
}
// ...
};
using MessagePointer = std::shared_ptr<MessageAbstract const>;
} // namespace Example
using namespace Example;
int main() {
MessagePointer message_ptr{/* Creation Method / Factory Method */};
switch (message_ptr->header().type()) {
case MessageTypes::kFooMessage: {
std::shared_ptr<FooMessage const> foo_message{std::static_pointer_cast<FooMessage const>(message_ptr)};
foo_message->specific_method_for_class_foo_message();
// ...
break;
}
case MessageTypes::kBarMessage: {
std::shared_ptr<BarMessage const> bar_message{std::static_pointer_cast<BarMessage const>(message_ptr)};
bar_message->specific_method_for_class_bar_message();
// ...
break;
}
default:
// Throw exception.
break;
}
return 0;
}
答案 8 :(得分:-1)
我没有看到任何有关此问题的答案,但您无法通过网络发送C ++对象并期望它们完好无损地到达。虚拟表是根据发送计算机中的内存状态设置的,接收计算机很可能不会在同一个地方放置东西。这通常也会导致RTTI失败(这是dynamic_cast使用的),因为RTTI通常与vtable一起实现。