我们可以使用多态(继承+虚函数)来概括在公共基类型下的不同类型,然后引用不同的对象,就好像它们属于同一类型一样。
使用dynamic_cast
似乎是完全相反的方法,因为在本质上我们在决定我们想要采取什么行动之前检查对象的特定类型。
是否有任何已知的例子,无法使用传统的多态性实现,就像使用dynamic_cast
一样容易实现?
答案 0 :(得分:6)
每当你发现自己想在基类中使用像“IsConcreteX”这样的成员函数时(编辑:或者更确切地说,像“ConcreteX * GetConcreteX”这样的函数),你基本上就是在实现自己的dynamic_cast
。例如:
class Movie
{
// ...
virtual bool IsActionMovie() const = 0;
};
class ActionMovie : public Movie
{
// ...
virtual bool IsActionMovie() const { return true; }
};
class ComedyMovie : public Movie
{
// ...
virtual bool IsActionMovie() const { return false; }
};
void f(Movie const &movie)
{
if (movie.IsActionMovie())
{
// ...
}
}
这可能看起来比dynamic_cast
更清晰,但仔细观察后,你很快就会意识到除了“邪恶”dynamic_cast
不再出现之外,你没有获得任何东西。你的代码(如果你没有使用一个没有实现dynamic_cast
的古老编译器!:))。更糟糕的是 - “自编动态强制转换”方法冗长,容易出错且重复,而dynamic_cast
可以很好地工作,而且在类定义中没有任何额外的代码。
所以真正的问题应该是,是否存在基类了解具体派生类的情况。答案是:通常它没有,但你无疑会遇到这种情况。
用非常抽象的术语来思考软件的一个组件,它将对象从一个部分(A)传输到另一个部分(B)。这些对象属于Class1
或Class2
,其中Class2
是-a Class1
。
Class1
^
|
|
Class2
A - - - - - - - -> B
(objects)
但是, B仅对Class2
有一些特殊处理。 B可以是系统的完全不同的部分,由不同的人或遗留代码编写。在这种情况下,您希望重复使用A-to-B通信而不进行任何修改,您也可能无法修改B.因此,明确询问您是否在该行的另一端处理Class1
或Class2
个对象可能是有意义的。
void receiveDataInB(Class1 &object)
{
normalHandlingForClass1AndAnySubclass(object);
if (typeid(object) == typeid(Class2))
{
additionalSpecialHandlingForClass2(dynamic_cast<Class2 &>(object));
}
}
以下是不使用typeid
的替代版本:
void receiveDataInB(Class1 &object)
{
normalHandlingForClass1AndAnySubclass(object);
Class2 *ptr = dynamic_cast<Class2 *>(&object);
if (ptr != 0)
{
additionalSpecialHandlingForClass2(*ptr);
}
}
如果Class2
不是叶子类(即如果可能有进一步派生的类),这可能更好。
最后,它通常归结为您是从一开始就设计一个包含所有部件的整个系统,还是必须在以后阶段修改或调整部分系统。但如果您发现自己遇到了类似上述问题的问题,您可能会认为dynamic_cast
是在正确的情况下找到合适工作的正确工具。
答案 1 :(得分:1)
它允许您执行只能对派生类型执行的操作。但这通常暗示重新设计是有序的。
struct Foo
{
virtual ~Foo() {}
};
struct Bar : Foo
{
void bar() const {}
};
int main()
{
Foo * f = new Bar();
Bar* b = dynamic_cast<Bar*>(f);
if (b) b->bar();
delete f;
}
答案 2 :(得分:1)
我无法想到任何不可能使用虚函数的情况(除了boost:any
之类的东西以及类似的“丢失原始类型”工作)。
但是,我发现自己在Pascal编译器中使用dynamic_cast
几次,我目前用C ++编写。主要是因为它比将十几个虚函数添加到基类是一个“更好”的解决方案,当你已经(应该)知道对象是什么类型时,它只在一两个地方使用。目前,在大约4300行代码中,有6个dynamic_cast
实例 - 其中一个实例可能通过实际存储类型作为派生类型而不是基类型来“修复”。
在某些地方,我使用ArrayDecl* a = dynamic_cast<ArrayDecl*>(type);
之类的东西来确定type
确实是一个数组声明,而不是在访问索引时使用非数组类型作为基础的人(我以后还需要a
来访问数组类型信息。同样,将所有虚函数添加到基类TypeDecl
类将提供许多函数,这些函数通常不返回任何有用的函数(例如NULL
),并且除非您已经知道类是(或者至少应该是派生类型之一。例如,了解数组的范围/大小对于非数组的类型是无用的。
答案 3 :(得分:1)
没有优势。有时dynamic_cast
对于快速破解很有用,但通常最好正确设计类并使用多态。可能存在这样的情况:由于某些原因,无法修改基类以添加必要的虚函数(例如,它来自我们不想修改的第三方),但仍然dynamic_cast
用法应该是例外,而不是规则。
将所有内容添加到基类不常用的常用参数不能正常工作,因为访问者模式(参见例如http://sourcemaking.com/design_patterns/visitor/cpp/2)以更有条理的方式解决了这个问题,纯粹是使用多态 - 使用Visitor,你可以保持基类较小,仍然使用虚函数而不进行强制转换。
答案 4 :(得分:0)
dynamic_cast
需要在基类指针上用于向下转换,但仅在派生类中可用。使用它没有任何好处。当从基类中重写虚函数时,这是一种安全向下转换的方法。检查返回值上的空指针。你是正确的,它用于没有虚函数推导的地方。