gpt4 book ai didi

c++ - dynamic_cast 上下文中的 "capability query"是什么,为什么有用?

转载 作者:行者123 更新时间:2023-11-30 04:12:30 25 4
gpt4 key购买 nike

我正在阅读一些关于 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 只有在 d2 可以从 baz 函数中的 b 成功向下转换时才会执行。那么这里的问题是什么?!

我的第二个问题是为什么第二个代码块被认为是好的?以及这如何解决这个问题,我一开始就不明白。

仅供引用,我的 google 搜索 capability query 返回了 http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Capability_Query这似乎基本上是代码块 1 而不是代码块 2。我仍然不明白为什么额外的空基类被认为是更好的做法?

编辑:这是我能想到的最好的答案。由于在 baz 内部我向下转换为指针类型而不是引用,如果向下转换不成功,我将得到一个 Null 指针而不是 std: :坏 Actor 。因此,假设转换出错并且我确实得到 NULL pointer ,但是如果我不应该执行 Null->foo 并且我可能忘记测试 NULL 怎么办,那么代码块 1 可能是一个问题。代码块 2 解决此问题的方法是添加一个空类。即使

dynamic_cast<capability_query *> (b)

失败,我得到一个空指针,你不能执行null->foo 因为在 capability_query 类中,这个 foo 方法是纯虚拟的。这只是一个猜想,但也许我是在正确的道路上??!!

最佳答案

学术上的答案是,在面向对象的设计中,您不应该依赖于实现,即具体类。相反,您应该依赖高级组件,例如接口(interface)抽象基类。您可以阅读更多相关信息 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 中的任何内容。这是一个解耦设计,更易于维护。 这就是为什么向下转换和依赖具体类是不好的做法的原因,而不是只使用高级接口(interface)(在一般情况下)。

在您的第二个示例中,您使用一个单独的类来评估派生类的请求行为是否可用。当您分离(并封装)行为控制代码时,这可能会好一些。尽管如此,它仍然会对您的实现产生依赖性,并且每次实现发生变化时,您可能需要更改行为控制代码。

关于c++ - dynamic_cast 上下文中的 "capability query"是什么,为什么有用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19823223/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com