5

(这个问题可能应该参考 Stroustrup 来回答。)

能够请求指向最派生类的指针似乎非常有用,如下所示:

class Base { ... };
class DerivedA { ... };
class DerivedB { ... };
class Processor
{
  public:
  void Do(Base* b) {...}
  void Do(DerivedA* d) {...}
  void Do(DerivedB* d) {...}
};

list<Base*> things;
Processor p;
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i)
{
    p.Do(CAST_TO_MOST_DERIVED_CLASS(*i));
}

但是这种机制在 c++ 中没有提供。为什么?

更新,激励示例:

假设您没有 Base 和 Derived 以及 Processor,而是:

class Fruit
class Apple : public Fruit
class Orange: public Fruit

class Eater
{
   void Eat(Fruit* f)  { ... }
   void Eat(Apple* f)  { Wash(f); ... }
   void Eat(Orange* f) { Peel(f); ... }
};

Eater me;
for each Fruit* f in Fruits
    me.Eat(f);

但这在 C++ 中很难做到,需要像访问者模式这样的创造性解决方案。那么,问题是:为什么在 C++ 中这样做很棘手,而像“CAST_TO_MOST_DERIVED”这样的东西会让它变得更简单?

更新:维基百科无所不知

我认为 Pontus Gagge 有一个很好的答案。从关于Multiple Dispatch的 Wikipedia 条目中添加这一点:

“Stroustrup 提到他喜欢The Design and Evolution of C++中的 Multi-methods 概念,并考虑在 C++ 中实现它,但声称无法找到有效的示例实现(类似于虚函数)并解决一些可能的类型歧义问题. 他接着说,虽然这个特性仍然很好,但它可以使用双重调度或基于类型的查找表来近似实现,如上面的 C/C++ 示例中所述,因此对于未来的语言来说是一个低优先级特性修订。”

作为背景,您可以阅读有关Multi-Methods的一些摘要,这比我提到的那种调用要好,因为它们可以正常工作。

4

10 回答 10

8

可能是因为这就是虚函数为您所做的。当您通过基类指针或引用调用它时,将调用最接近最派生类的虚函数的实现。

于 2010-06-16T14:19:17.397 回答
8

首先,C++ 确实允许您以数字形式请求指向最派生类的指针(即,只是地址的数值)。这就是要做dynamic_castvoid*

其次,没有办法在最派生类的确切类型中获得指向最派生类的指针。在 C++ 中,强制转换使用静态类型静态类型是一个编译时概念。基于类型的函数重载也是一个编译时过程。在您的情况下,在编译时不知道确切的最派生类型,这就是为什么不能强制转换为它并且无法解决它的重载的原因。在 C++ 语言领域中,要求进行这样的转换没有意义的。

您尝试实现的内容(如果我正确理解您的意图)是通过完全不同的方式实现的,而不是通过演员表实现的。例如,阅读双重调度

于 2010-06-16T14:36:30.787 回答
6

因为 i 的类型在编译时无法确定。因此编译器不知道要生成哪个函数调用。C++ 仅支持一种动态调度方法,即虚函数机制。

于 2010-06-16T14:22:16.957 回答
4

它是使用虚函数调用来调用的。将处理器* 传递给 DerivedA/B 的虚拟方法。不是反过来。

没有提供任何机制,因为它完全没有必要和多余。

我发誓,我在一两天前回答了这个确切的问题。

于 2010-06-16T14:17:33.627 回答
4

您的建议相当于switch运行时类型上的 a,调用其中一个重载函数。正如其他人所指出的,您应该使用您的继承层次结构,而不是反对它:在您的类层次结构中使用虚拟而不是在它之外调度。

也就是说,这样的事情对于双重调度可能很有用,特别是如果你也有一个Processors. 但是编译器将如何实现它呢?

首先,您必须在运行时提取所谓的“最重载类型”。可以做到,但是您将如何处理例如多重继承和模板?语言中的每个特性都必须与其他特性很好地交互——而 C++ 有很多特性!

其次,要使您的代码示例正常工作,您必须根据运行时类型获得正确的静态重载(C++ 在设计时不允许这样做)。您是否希望它遵循编译时查找规则,尤其是使用多个参数?您是否希望此运行时调度也考虑Processor层次结构的运行时类型,以及它们添加了哪些重载?您希望编译器自动将多少逻辑添加到您的运行时调度程序中?您将如何处理无效的运行时类型?该功能的用户是否会意识到看似简单的强制转换和函数调用的成本和复杂性?

总而言之,我想说该功能实现起来很复杂,在实现和使用中都容易出错,并且仅在极少数情况下有用。

于 2010-06-16T14:46:18.437 回答
2

在 C++ 中,重载解析发生在编译时。您的示例需要*i在运行时确定实际类型。要在运行时完成它需要运行时类型检查,并且由于 C++ 是一种面向性能的语言,它有目的地避免了这种成本。如果你真的想这样做(我很想看到一个更现实的例子)你可以 dynamic_cast 到最派生的类,然后如果它失败到第二个最派生的类,依此类推,但这需要知道前面的类层次结构。并且预先知道完整的层次结构可能是不可能的——如果 DerivedB 类位于公共标头中,则可能另一个库使用它并创建了一个更加派生的类。

于 2010-06-16T14:48:17.347 回答
2

您正在寻找双重调度。它可以在 C++ 中完成,如该链接所示,但它并不漂亮,它基本上涉及使用两个相互调用的虚函数。如果您无法修改继承树中的某些对象,您可能也无法使用此技术。

于 2010-06-16T14:50:31.230 回答
1

这在 C++ 中是不可能的,但是使用访问者设计模式可以轻松实现您想要实现的目标:

class Base
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class DerivedA
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class DerivedB
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class BaseVisitor
{   
    virtual void visit(Base* b) = 0;
    virtual void visit(DerivedA* d) = 0;
    virtual void visit(DerivedB* d) = 0;
};

class Processor : public BaseVisitor
{
    virtual void visit(Base* b) { ... }
    virtual void visit(DerivedA* d) { ... }
    virtual void visit(DerivedB* d) { ... }
};

list<Base*> things;
Processor p;
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i)
{
    (*i)->visit(p);
}
于 2010-06-16T14:57:37.677 回答
1

为什么 C++ 没有它?也许创作者从来没有想过它。或者他们认为它不够合适或不够有用。或者实际上尝试用这种语言来做这件事可能存在问题。

关于最后一种可能性,这里有一个思想实验:

假设存在此功能,因此编译器将编写代码来检查指向的动态类型并调用适当的重载。现在还可以说代码的一个单独部分具有class DerivedC : Base {...};. 并说相应的Processor::Do重载加。

考虑到所有这些,程序在尝试选择适当的重载时应该怎么做?在编译时无法捕捉到这种差异。它是否应该尝试爬上类层次结构以找到与基类匹配的函数?它应该抛出一个特殊的异常吗?它应该崩溃吗?有没有其他的可能?在不知道代码和类层次结构的意图的情况下,编译器实际上可以自行做出任何合理的选择吗?

是的,自己编写这样的功能也会遇到同样的问题,但是程序员可以完全控制选择行为,而不是编译器。

于 2010-06-16T23:36:16.137 回答
0

C++ 在关联类型的上下文中解释数据。当您将 DerivedA* 或 DerivedB* 的实例存储在列表中时,该关联类型必须是 Base*。这意味着编译器本身不能再确定这些是指向子类之一而不是基类的指针。虽然理论上您可以通过查看关联类型的继承来强制转换为 LESS 派生类,但在编译时根本无法获得执行所需操作所需的信息。

于 2010-06-16T14:30:56.180 回答