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

标签 c++ dynamic-cast

我正在阅读一些关于 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/

相关文章:

c++ - 带有 TTF 的 SDL 内存泄漏

c++ - 如何正确找出lambda的返回类型

c++ - 使用 OpenSSL 构建 QT 期间出错

c++ - 什么可能导致 dynamic_cast 崩溃?

c++ - 在 C++ 中使用 RTTI 将对象转换为正确的类型

c# - 创建一个 Youtube 机器人

c++ - 如何检测一个类是否有成员变量?

c++11 - 在非多态类型的情况下,dynamic_cast<void*> 是否保证工作(即等于 static_cast<void*>)?

c++ - 为什么 reinterpret_cast 在某些情况下有效而在其他情况下无效?

c++ - 是否可以从一个基类动态转换到另一个基类?