与父项同名的 C++ 成员变量没有完全限定(只要它是可扩展的?)
Posted
技术标签:
【中文标题】与父项同名的 C++ 成员变量没有完全限定(只要它是可扩展的?)【英文标题】:C++ member variable with same name as in parent works without full qualification (as long as it is expansive?) 【发布时间】:2021-05-11 14:34:22 【问题描述】:我已经阅读了几个处理具有相同名称的成员变量的父类和子类的问题,并且公认的方法似乎是完全限定所需的所需变量(例如,在子类的方法体中) Parent::foo
或 Child::foo
一切都应该正常。
不过,我对我遇到的以下示例感到好奇。我正在尝试准备一个层次结构的类,每个类都代表一些需要特定配置的小工具。我要传递的配置一个专用的struct
。
因为我将拥有同一系列的不同小工具类型,所以我有一个父小工具(具有虚拟方法和所有特定小工具类型共有的方法)和父配置(包含所有小工具类型所需的所有配置参数)。除此之外,我将有子小工具类覆盖父虚拟方法,并使用任何小工具类型所需的父方法。这些子小工具需要更具体的配置参数,而其他特定(近亲)小工具类型不需要这些参数,因此超出了父通用小工具的范围。
我了解到我可以在父级和子级中调用该配置config
,并且从子类中的方法访问该配置的成员变量(那些配置)可以正常工作而无需完全限定。这是一个玩具示例:
#include <iostream>
struct ConfigBase
float x;
explicit ConfigBase(float x_): x(x_)
;
struct ConfigDerived: public ConfigBase
float y;
explicit ConfigDerived(float x_, float y_): ConfigBase(x_), y(y_)
;
class ObjectBase
public:
ConfigBase config;
explicit ObjectBase(ConfigBase config_): config(config_)
;
class ObjectDerived: public ObjectBase
public:
static ConfigBase castToParentConfig(const ConfigDerived& config)
ConfigBase parentConfig = config; // Here I am slicing away some member variables of my ConfigDerived instance
return config;
;
void printMemberVars()
std::cout << "x: " <<config.x << std::endl;
std::cout << "y: " <<config.y << std::endl;
ConfigDerived config;
explicit ObjectDerived(ConfigDerived config_): ObjectBase(castToParentConfig(config_)), config(config_)
;
int main()
ConfigDerived config(1.f, 2.f);
ObjectDerived foo(config);
foo.printMemberVars();
return 0;
上面的可执行文件可以正常工作:
x: 1
y: 2
Process finished with exit code 0
我能想到的只有三个选项:
-
编译器特意构建了一个包罗万象的
config
,它包含一个struct
一个父子的所有成员变量,这样我就可以访问所有配置成员,就好像只有一个config
一样。李>
这是我初始化“子成员变量的一部分”、切掉初始化的部分并将修剪后的版本传递到上游以让父级以相同的名称初始化变量的剩余部分的意外结果。李>
C++ 有某种硬编码的成员解析顺序。如果我在父项和子项中查找具有相同名称的成员变量的属性,它首先会尝试在父项中找到它,如果失败,则在子项中(或相反)
最初我期望config.x
根本无法访问,因为在子类中定义了同名的成员变量,并且在访问它时缺乏完整的限定。这种期望显然是不合理的。
谁能解释一下这里发生了什么?这被认为是不好的风格还是对这种 C++ 功能的不当使用?
【问题讨论】:
子类中有哪些同名的成员变量?我能看到的唯一ConfigDerived::x
是它继承自ConfigBase
的那个。
你一次混淆了太多东西。我建议你从一个更简单的例子开始。只看ConfigBase
和ConfigDerived
。在ConfigDerived
中有成员x
和y
。
ObjectDerived
确实有两个成员config
,但是因为两者都有一个x
成员,所以你的推理路线有点……糊涂
当您在ObjectDerived
中使用config
时,您正在使用它的ConfigDerived
成员。该对象与ObjectBase
中的同名ConfigBase
成员完全无关。
ObjectDerived::config
不仅仅是“ConfigDerived
的非ConfigBase
部分”,而是整个ConfigDerived
。由于ConfigDerived::x
存在,foo.config.x
完全有效。这确实意味着您正在存储一些重复的信息,因为 foo 的阴影 ConfigBase
名为 config
是与其 ConfigDerived
名为 config
的单独对象。
【参考方案1】:
最初我以为 config.x 根本无法访问,...
我不知道你为什么这么想。 ObjectDerived
类有一个ConfigDerived
成员,而ConfigDerived
结构派生自ConfigBase
- 所以它既有y
(它的“自己的”)和x
(继承自基)元素。 注意: x
成员作为(单独的)config
数据成员的一部分从基本结构继承;它不从基类config
结构继承x
,这是一个独立的对象,对派生类隐藏。
这被认为是不好的风格还是对这种 C++ 功能的不当使用?
最终,这是一个见仁见智的问题。但是,我会说它是糟糕的编码风格,可能会导致意外和令人困惑的结果(正如您所指出的)。我测试你的代码的两个编译器都同意我的观点。
Visual Studio 2019 中的 MSVC 编译器发出以下警告(两次 - 在您的 castToParentConfig
函数的每一行上一次:
警告 C26437:请勿切片 (es.63)。
clang-cl 编译器(也在 VS-2019 中)针对同一问题给出不同的警告:
警告:“ObjectDerived”阴影的非静态数据成员“config” 成员继承自类型“ObjectBase”[-Wshadow-field] 警告:参数“config”遮蔽了从“ObjectBase”类型继承的成员 [-Wshadow-field]
在您发布的相当简单的代码中,这些“阴影”和“切片”问题相对来说是无害的;但是,在更一般的情况下,将基类的数据成员隐藏在派生类中可能会导致更严重的问题,尤其是在成员类型不那么密切相关的情况下。
这里是Microsoft's policy on slicing的总结:
编译器允许切片,可以看作是 危险的隐式演员表。即使是故意的,没有 导致直接的问题,它仍然非常不鼓励,因为它 通过强制附加要求使代码相当难以维护 相关数据类型。如果类型是多态的,则尤其如此 或涉及资源管理。
【讨论】:
据我从上面的答案中了解到,您的这部分答案“因此它同时具有 y (它的'自己的')和 x (从基础继承的)元素。”不适合,因为ConfigDerived
的x
成员变量也是它自己的。否则,如果我用另一个具有相同名称config
的对象覆盖它,我将无法访问它。它之所以有效,是因为ConfigDerived
也恰好有一个x
成员变量,它也恰好与ConfigBase.x
保持相同的值。我错了吗?
@mosegui 是的 - 我需要澄清一下。派生类中有两个名为config
的独立对象,但一个被另一个隐藏。我的意思是派生类中的派生结构从基结构的定义继承其x
成员。【参考方案2】:
结构默认情况下具有公共访问权限,因此在您当前的实现中,它们将是公共的并且无需限定即可使用。
https://www.cplusplus.com/doc/tutorial/classes/
【讨论】:
以上是关于与父项同名的 C++ 成员变量没有完全限定(只要它是可扩展的?)的主要内容,如果未能解决你的问题,请参考以下文章