我正在阅读关于dynamic_cast的一些C ++材料,并且以下实践被认为是错误的:
class base{};
class derived1 d1 :public base{};
class derived2 d2 :public base
{
public:
void foo(){}
};
void baz(base *b)
{
if (derived2 *d2= dynamic_cast<derived2 *> (b) )
{
d2-> foo();
}
}
对此的补救措施是使用空的纯虚拟基类来使用“功能查询”,如下所示:
class capability_query
{
public:
virtual void foo()= 0;
};
class base{};
class derived1 d1 :public base{};
class derived2 d2 :public base, public capability_query
{
public:
virtual void foo(){}
};
void baz(base *b)
{
if (capability_query *cq= dynamic_cast<capability_query *> (b) )
{
cq-> foo();
}
}
我的第一个问题是为什么第一个代码块被认为是坏的?
只有在foo
函数中可以从b成功降低d2时,才会执行我看到baz
的方式。那么这里的问题是什么?!
我的第二个问题是为什么第二个代码块被认为是好的?以及如何解决这个问题,我首先不了解这个问题。
仅供参考,我的谷歌搜索capability query
已返回http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Capability_Query
这似乎基本上是代码块1而不是代码块2。我仍然不明白为什么额外的空基类被认为是更好的做法?
修改
这里是最好的答案,我能想到of.Since内baz
我向下转换为指针类型,而不是引用,万一丧气不成功,我会得到一个空指针,而不是标准:: bad_cast。因此,假设强制转换并且我确实获得了NULL指针,但是如果我不应该执行Null->foo
并且如果我可能忘记测试NULL,那么代码块1可能是个问题。
代码块2修复此问题的方法是添加一个空类。即使
dynamic_cast<capability_query *> (b)
失败,我得到一个空指针,你无法执行
null->foo
因为在capability_query
类内部,此foo
方法是纯虚拟的。这只是一个猜想,但可能是我在正确的道路上?? !!
答案 0 :(得分:1)
学术答案是在面向对象设计中,你不应该依赖于实现,即具体的类。相反,您应该依赖于 interfaces 和抽象基类等高级组件。您可以阅读有关此design principle on Wikipedia的更多信息。
这样做的原因是解耦设计,使代码更易于管理和维护。
让我们看一个例子。你有一个基类和派生类:
struct Duck {
virtual ~Duck() {}
};
struct MallardDuck : public Duck {
void quack() const {
std::cout << "Quack!" << std::endl;
}
};
假设你有另一个带有参数Duck
的函数的类。
struct SoundMaker {
void makeSound(const Duck* d) {
if (const MallardDuck* md = dynamic_cast<const MallardDuck*>(d)) {
md->quack();
}
}
};
您可以使用以下类:
MallardDuck md;
SoundMaker sm;
sm.makeSound(&md);
哪个输出Quack!
。
现在让我们添加另一个派生类RubberDuck
:
struct RubberDuck : public Duck {
void squeak() const {
std::cout << "Squeak!" << std::endl;
}
};
如果您希望SoundMaker
使用课程RubberDuck
,则必须在makeSound
中进行更改:
void makeSound(const Duck* d) {
if (const MallardDuck* md = dynamic_cast<const MallardDuck*>(d)) {
md->quack();
} else if (const RubberDuck* rd = dynamic_cast<const RubberDuck*>(d)) {
rd->squeak();
}
}
如果你需要添加另一种鸭子并发出声音怎么办?对于您添加的每种新型鸭子,您必须在新鸭子类的代码和SoundMaker
中进行更改。这是因为您依赖具体实施。如果你可以在不改变SoundMaker
的情况下添加新的鸭子,那会不会更好?请查看以下代码:
struct Duck {
virtual ~Duck() {}
virtual void makeSound() const = 0;
};
struct MallardDuck : public Duck {
void makeSound() const override {
quack();
}
void quack() const {
std::cout << "Quack!" << std::endl;
}
};
struct RubberDuck : public Duck {
void makeSound() const override {
squeak();
}
void squeak() const {
std::cout << "Squeak!" << std::endl;
}
};
struct SoundMaker {
void makeSound(const Duck* d) {
d->makeSound(); // No dynamic_cast, no dependencies on implementation.
}
};
现在您可以像以前一样使用两种鸭子类型:
MallardDuck md;
RubberDuck rd;
SoundMaker sm;
sm.makeSound(&md);
sm.makeSound(&rd);
您可以根据需要添加任意数量的鸭子类型,而无需在SoundMaker
中更改任何内容。这是一个解耦设计,更容易维护。 这就是为什么下行和依赖具体类是不好的做法的原因,而只是使用高级接口(在一般情况下)。
在第二个示例中,您使用单独的类来评估派生类的请求行为是否可用。当你将行为控制代码分开(并且封装)时,这可能会更好一些。它仍然会为您的实现创建依赖关系,每次实现更改时,您可能需要更改行为控制代码。
答案 1 :(得分:1)
在d2->foo()
上调用foo的第一个示例违反了开放 - 封闭原则,在这种情况下,这意味着您应该能够在d2
中添加或删除功能而无需更改代码baz
(或其他任何地方)。代码:
void baz(base *b)
{
if (capability_query *cq= dynamic_cast<capability_query *> (b) )
{
cq-> foo();
}
}
表明baz
取决于类d2
的定义。如果某天删除了函数d2::foo()
,则还必须修改函数baz
,否则您将成为编译器错误。
但是,在改进版本中,如果作者决定通过删除基类foo
来删除d2的capability_query
功能,(或者确实如果foo
能力是添加到类d1
)函数baz
不需要修改,运行时行为将自动正确。