双重调度会产生“隐藏虚拟功能”警告,为什么?

如何解决双重调度会产生“隐藏虚拟功能”警告,为什么?

| 我想实现两个对象的交互,这些对象的类型派生自一个公共基类。存在默认的交互,并且一旦相同类型的对象交互,则可能会发生特定的事情。 这是使用以下双重调度方案实现的:
#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\"
}
关于此代码,我有两个非常不同的问题: 您认为这是一种合理的方法吗?您会提出一些不同的建议吗? 如果省略前两个
B
方法,则会收到编译器警告和错误,表明后两个
B
方法隐藏了
A
方法。这是为什么?不应将
A*
指针强制转换为
B*
指针? 更新:我刚刚发现
using A::PreCompose;
using A::PostCompose;
使错误和警告消失,但这为什么有必要? 更新2:这在这里得到了很好的解释:http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.9,谢谢。那我的第一个问题呢?对这种方法有何评论?     

解决方法

双重调度通常在C ++中以不同的方式实现,基类具有所有不同的版本(这使它成为维护的噩梦,但这就是这种语言)。尝试进行双重分派的问题在于,动态分派会在要调用该方法的对象中找到派生程度最大的类型
B
,但是自变量具有静态类型
A*
。由于
A
没有以
B*
作为参数的重载,因此调用
other->PreCompose(this)
会将
this
隐式转换为
A*
,并且您在第二个参数上只剩下一次分派。 作为一个实际的问题:为什么编译器会产生警告?为什么需要添加
using A::Precompose
指令? 原因是C ++中的查找规则。然后,编译器会遇到对
obj.member()
的调用,它必须查找标识符ѭ16and,并且它将从静态类型ѭ17starting开始,如果在该上下文中未能找到
member
,它将在层次结构中向上移动并在静态类型为
obj
的基数。 一旦找到第一个标识符,查找将停止并尝试将函数调用与可用的重载进行匹配,如果无法匹配该调用,则会触发错误。这里重要的一点是,如果无法匹配函数调用,则查找将不会在层次结构中进一步查找。通过添加
using base::member
声明,可以将基类中的标识符
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\" );
时,对象的静态类型为
derived
,并且查找将检查
derived
中的所有成员函数,标识符
foo
位于此处,并且查找不会向上进行。唯一可用的重载参数为
std::string const&
,并且编译器将添加隐式转换,因此,即使可能存在最佳潜在匹配(对于该调用,ѭ28than比
derived::foo(std::string const&)
更好的匹配),它将有效地调用:
d.derived::foo( std::string(\"Hi\") );
下一个表达式
d.foo( 5 );
的处理类似,在look24ѭ中开始查找,发现那里存在成员函数。但是参数
5
无法隐式转换为
std::string const &
,即使
base::foo(int)
中存在完美匹配,编译器也会发出错误。请注意,这是调用中的错误,而不是类定义中的错误。 在处理第三个表达式时,
b.foo( \"you\" );
对象的静态类型为
base
(请注意,实际对象为
derived
,但引用的类型为
base&
),因此查找将不会在ѭ24search中进行搜索,而是从
base
开始。它找到两个重载,并且其中一个重载是很好的匹配,因此它将称为
base::foo( const char* )
b.foo(5)
也是如此。 最后,在最派生的类中添加不同的重载时,将重载隐藏在基数中,但它不会将其从对象中删除,因此您可以通过完全限定调用来实际调用所需的重载(这将禁用查找并具有如果函数是虚拟的,则增加了跳过动态分派的副作用),因此
d.base::foo( \"there\" )
将根本不执行任何查找,而只是将调用分派到
base::foo( const char* )
。 如果在
derived
类中添加了
using base::foo
声明,则应将
base
中的所有ѭ26the重载添加到
derived
中的可用重载,调用and23ѭ会考虑
base
中的重载,并发现最佳重载为
base::foo( const char* );
,因此实际上将被执行为
d.base::foo( \"Hi\" );
在许多情况下,开发人员并不总是在思考查找规则的实际工作方式,并且可能令人惊讶的是,如果没有
using base::foo
声明,对
d.foo( 5 );
的调用就会失败,或者更糟的是,在很明显的情况下,将对
d.foo( \"Hi\" );
的调用分派给
derived::foo( std::string const & )
。过载比than42更严重。这是隐藏成员函数时编译器发出警告的原因之一。该警告的另一个很好的原因是,在许多情况下,当您实际上打算覆盖虚函数时,可能会错误地更改签名:
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
(忘记
const
限定词)时出现一个小错误,实际上意味着您正在创建其他函数签名。
derived::name
不是对
base::name
的替代,因此通过引用ѭ37to对
name
的调用不会分派给
derived::name
!     ,
using A::PreCompose;
using A::PostCompose;
makes the errors and warnings vanish,but why is this necessary?
如果使用与基类包含的名称相同的名称向派生类添加新功能,并且不覆盖基类中的虚函数,则新名称将隐藏基类中的旧名称。 这就是为什么您需要通过显式编写来取消隐藏它们的原因:
using A::PreCompose;
using A::PostCompose;
取消隐藏它们的另一种方法(在这种情况下)是,从您在发布的代码中完成的基类中覆盖虚函数。我相信代码可以很好地编译。     ,类是作用域,在基类中查找被描述为在封闭作用域中查找。 当查找一个函数的重载时,如果在嵌套的函数中找到了一个函数,则不会在封闭范围内查找。 这两个规则的结果是您实验的行为。添加using子句是从封闭范围导入定义的,这是正常的解决方案。     

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-