是否可以强制超类中的非常量虚拟方法优先于子类中同名的 const 方法?

Posted

技术标签:

【中文标题】是否可以强制超类中的非常量虚拟方法优先于子类中同名的 const 方法?【英文标题】:Is it possible to force non-const virtual methods from superclasses to take precedence over const methods of the same name in a subclass? 【发布时间】:2018-04-10 17:46:29 【问题描述】:

考虑以下类定义:

class foo 
  virtual absl::Span<const Input *const> GetInputs() const = 0;

  virtual absl::Span<Input *const> GetInputs() 
    auto mutable_inputs = GetMutableInputs();
    return absl::MakeSpan(mutable_inputs.begin(), mutable_inputs.end());
  


class bar : public foo 
  absl::Span<const Input *const> GetInputs() const override 
    return absl::MakeConstSpan(inputs_);
  

当调用bar.GetInputs() 时,似乎找到的唯一实现是返回一系列常量输入的实现。如果我有一个 bar 的实例,并且想要创建一个非常量输入范围,那么我必须将 bar 转换为 foo,然后调用 GetInputs

如果我将 bar 转换为 foo,然后调用 GetInputs,我就可以将结果分配给非 const 输入的范围。为什么编译器无法识别具有正确返回类型的继承的非常量方法?有没有办法让子类识别该方法?

也就是说,有没有办法让下面的代码编译:

absl::Span<Input *const> tmp = bar.GetInputs()

【问题讨论】:

GetInputs在派生类中标记为const;它不能调用非 const 成员函数,除非你抛弃 const。 不确定你的意思。如果我有一个 Bar 的非常量实例,那么该实例应该能够调用 const 实现或非常量继承方法。 const 成员函数承诺不会修改它所应用的对象的任何成员。调用非常量成员函数会破坏这个承诺。如果你需要调用非常量成员函数,不要标记你的函数const 我不是在尝试调用 const 方法,而是在尝试调用从 Foo 继承的同名非 const 方法。我的目标是能够通过 Bar 的非常量实例调用从 Foo 继承的继承的非常量 GetInputs 方法。 我在问题中又添加了一行,以便更明确地说明我想要的行为。但重申一下,如果我有 Bar 的非 const 实例,我的目标是从 Foo 获取继承的非 const 实现 GetInput 被调用,而不是 Bar 中的 const 实现。 【参考方案1】:

如果我理解您的问题,它与虚函数或 const 的“优先级”无关,而是普通的旧“名称隐藏”。

#include <iostream>
class Base 
public:
    virtual void f(int)     std::cout << "Base(int)\n"; 
    virtual void f(double)  std::cout << "Base(double)\n"; 
;

class Derived : public Base 
public:
    virtual void f(double)  std::cout << "Derived(double)\n"; 
;

int main() 
    Derived d;
    int x=0;
    d.f(x);

输出:派生(双)

问题是,名称查找并不像您期望的那样工作。 对于给定的范围,它会搜索名称以构建重载集。在 Derived 的上下文中,只有一个 f(),所以当找到它时,编译器停止进一步搜索更多的重载

它找到 Derived(double) 并且这是整个重载集,因此它被选中。当您将派生类转换为对基类的引用,然后调用某些东西时,会考虑两个函数(在基类中声明),并且重载决策会选择最佳匹配。

现在,通常,对于多态类型,您是根据对基的指针/引用来处理对象,所以这不是问题。但是,如果您直接调用派生类(可能是从派生的成员内部调用?),那么就会出现派生声明隐藏基名的问题。

要使基名在派生类中可见,很简单:

class Derived : public Base 
public:
    using base::f; // <<<<<<<< just add this
    virtual void f(double)  std::cout << "Derived(double)\n"; 
;

【讨论】:

感谢您的解释,克里斯。我有一个后续问题要问你。如果我将 base 中的方法重命名为 f_mutable,则 f_mutable 可以从 Derived 调用。为什么编译器在这种情况下搜索Base构建重载集,而在名称相同的情况下不搜索Base构建重载集? 它搜索的是一系列范围。首先是当前班级,如果没有找到,则上报父母,依此类推。它一次一个地搜索名称,并且类似地在封闭的名称空间中搜索,直到它用完更高的范围。但通常,一旦在一个范围内找到名称,它就会停止搜索外部范围。这就是为什么内部作用域可以“隐藏”外部作用域中的名称......它们在看到外部作用域之前停止搜索。派生中没有 f_mutable,它继续到父范围。见en.cppreference.com/w/cpp/language/unqualified_lookup【参考方案2】:

你应该添加

using foo::GetInputs;

bar 类中公开基类函数。

如果对象是非常量的,你将能够调用基类函数

【讨论】:

以上是关于是否可以强制超类中的非常量虚拟方法优先于子类中同名的 const 方法?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在 C++ 中的超类中拥有子类的成员

是否可以从属于子类元素的子类访问超类中的属性?

超类中的受保护方法在不同包中的子类中是不是可见? [复制]

父类中的方法被覆盖以及子类调用父类覆盖的方法

8- 类

当子类使用val实现它时,超类中的Scala抽象方法为null?