Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/158.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 双重分派会产生“隐藏虚拟函数”警告,为什么?_C++_Double Dispatch - Fatal编程技术网

C++ 双重分派会产生“隐藏虚拟函数”警告,为什么?

C++ 双重分派会产生“隐藏虚拟函数”警告,为什么?,c++,double-dispatch,C++,Double Dispatch,我想实现两个对象之间的交互,这两个对象的类型派生自一个公共基类。有一个默认的交互,一旦相同类型的对象交互,特定的事情可能会发生。 这是使用以下双重调度方案实现的: #include <iostream> class A { public: virtual void PostCompose(A* other) { other->PreCompose(this); } virtual void PreCompose(A* other)

我想实现两个对象之间的交互,这两个对象的类型派生自一个公共基类。有一个默认的交互,一旦相同类型的对象交互,特定的事情可能会发生。 这是使用以下双重调度方案实现的:

#include <iostream>

class A
{
public:
  virtual void PostCompose(A* other)
    {
      other->PreCompose(this);
    }
  virtual void PreCompose(A* other)
    {
      std::cout << "Precomposing with an A object" << std::endl;
    }
};

class B : public A
{
public:
  virtual void PostCompose(A* other) // This one needs to be present to prevent a warning
    {
      other->PreCompose(this);
    }
  virtual void PreCompose(A* other) // This one needs to be present to prevent an error
    {
      std::cout << "Precomposing with an A object" << std::endl;
    }
  virtual void PostCompose(B* other)
    {
      other->PreCompose(this);
    }
  virtual void PreCompose(B* other)
    {
      std::cout << "Precomposing with a B object" << std::endl;
    }
};

int main()
{
  A a;
  B b;
  a.PostCompose(&a); // -> "Precomposing with an A object"
  a.PostCompose(&b); // -> "Precomposing with an A object"
  b.PostCompose(&a); // -> "Precomposing with an A object"
  b.PostCompose(&b); // -> "Precomposing with a B object"
}
使错误和警告消失,但为什么这是必要的

更新2:这里有很好的解释:,谢谢。我的第一个问题呢?对这种方法有何评论

using A::PreCompose;
using A::PostCompose;
makes the errors and warnings vanish, but why is this necessary?
如果使用与基类包含的名称相同的名称向派生类添加新函数,并且不重写基类中的虚拟函数,则新名称会对基类隐藏旧名称

这就是为什么您需要通过明确的文字将它们隐藏起来:

using A::PreCompose;
using A::PostCompose;

在这种特殊情况下,取消隐藏它们的另一种方法是,重写您在发布的代码中所做的基类中的虚拟函数。我相信代码可以很好地编译。

类是作用域,基类中的查找被描述为在封闭的作用域中查找

查找函数重载时,如果在嵌套的函数中找到函数,则不会在封闭范围中查找


这两条规则的结果就是你的实验行为。添加使用子句从封闭范围导入定义,是正常的解决方案。

双调度通常在C++中不同地实现,基类具有不同的版本,这使得它成为维护噩梦,但这就是语言的方式。尝试双重分派的问题是,动态分派将找到调用方法的对象的最派生类型B,但参数具有静态类型A*。由于A没有以B*为参数的重载,那么调用other->PreComposethis将隐式地将其向上转换为A*,并且在第二个参数上只剩下一个分派

至于实际问题:为什么编译器会产生警告?为什么我需要使用A::Precompose指令添加

原因是C++中的查找规则。然后编译器遇到对obj.member的调用,它必须查找标识符成员,并且它将从obj的静态类型开始查找,如果它在该上下文中找不到成员,它将在层次结构中向上移动,并在obj的静态类型的基中查找

一旦找到第一个标识符,查找将停止并尝试将函数调用与可用重载相匹配,如果无法匹配调用,则将触发错误。这里重要的一点是,如果函数调用无法匹配,则查找将不会在层次结构中进一步查找。通过添加using base::member声明,您将把标识符成员从基类带入当前范围

例如:

struct base {
   void foo( const char * ) {}
   void foo( int ) {}
};
struct derived : base {
   void foo( std::string const & ) {};
};
int main() {
   derived d;
   d.foo( "Hi" );
   d.foo( 5 );
   base &b = d;
   b.foo( "you" );
   b.foo( 5 );
   d.base::foo( "there" );
}
当编译器遇到表达式d.foo Hi时;对象的静态类型是派生的,查找将检查派生中的所有成员函数,标识符foo位于其中,并且查找不会向上进行。唯一可用重载的参数是std::string const&,编译器将添加一个隐式转换,因此,即使可能存在最佳匹配,base::foocont char*也比派生的::foostd::string const*更匹配&对于该调用,它将有效地调用:

d.derived::foo( std::string("Hi") );
下一个表达式d.foo 5;类似地,查找从派生中开始,并发现其中有一个成员函数。但是参数5不能隐式地转换为std::string const&并且编译器将发出错误,即使base::fooint中存在完美匹配。请注意,这是调用中的错误,而不是类定义中的错误

在处理第三个表达式时,b.foo you;对象的静态类型是基类注释,即实际对象是派生的,但引用的类型是Base&,因此查找不会在派生中进行搜索,而是从基开始。它找到了两个重载,其中一个是很好的匹配,因此它将调用base::foo const char*。b.foo5也是如此

最后,虽然在最派生的类中添加不同的重载会隐藏基中的重载,但它不会将它们从对象中删除,因此您可以通过完全限定调用来实际调用所需的重载,该调用将禁用查找,并具有附加的副作用,即如果函数是虚拟的,则跳过动态调度,因此,d.base::foo那里根本不会执行任何查找,只需将调用分派给base::foo const char*

如果您向派生类添加了using base::foo声明,那么您将把base中foo的所有重载添加到派生类中的可用重载中,并调用d.foo Hi;将考虑基数中的重载,并发现最佳的超载是Base::FO const char;因此它实际上将作为d.base::foo-Hi执行

在许多情况下,开发 lopers并不总是考虑查找规则实际上是如何工作的,对d.foo5的调用可能会令人惊讶;在没有使用base::foo声明的情况下失败,或者更糟的是,调用d.foo Hi时失败;被调度到派生::foo std::string const&当它显然是比base::foo const char*更糟糕的重载时。这就是为什么编译器在隐藏成员函数时发出警告的原因之一。该警告的另一个很好的理由是,在许多情况下,当您实际上打算重写虚拟函数时,您可能会错误地更改签名:

struct base {
   virtual std::string name() const {
      return "base";
   };
};
struct derived : base {
   virtual std::string name() {        // missing const!!!!
      return "derived";
   }
}
int main() {
   derived d; 
   base & b = d;
   std::cout << b.name() << std::endl; // "base" ????
}

在试图重写成员函数名时出现一个小错误,忘记了常量限定符,这意味着您实际上正在创建一个不同的函数签名。派生::name不是对base::name的重写,因此通过对base的引用对name的调用不会被分派到派生::name

是的,谢谢你。事实上,我刚刚找到了一个很好的解释。我应该在问之前发现,对不起。@lytenyn:没关系,不用说对不起:你真的应该把这分成两个问题。他们完全没有关系。一方面是查找问题,另一方面是双重分派问题,它们完全不相关。关于第一个问题:这种方法有缺陷。我已经对双重分派提供了一个简短的答案,同时对查找问题提供了一个较长的答案,但我不确定它有多清晰…@dribeas:谢谢!我又问了问题的双重派遣部分。如果你能在那里启发我那就太好了!谢谢,我没有看到双重调度问题。我将提出第二个问题,说明我的双重分派问题的细节,因为很遗憾,在基类中实现所有内容都是不可能的。
struct base {
   virtual std::string name() const {
      return "base";
   };
};
struct derived : base {
   virtual std::string name() {        // missing const!!!!
      return "derived";
   }
}
int main() {
   derived d; 
   base & b = d;
   std::cout << b.name() << std::endl; // "base" ????
}