如何在 C++ 中模拟覆盖父函数(不隐藏)?

Posted

技术标签:

【中文标题】如何在 C++ 中模拟覆盖父函数(不隐藏)?【英文标题】:How can I simulate overriding a parent function (not hiding) in c++? 【发布时间】:2019-01-23 17:07:12 【问题描述】:

从阅读此答案 (Is it possible to override a function in C++ child class without using virtual keyword to the function in parent class which is abstract?) 可以看出,不能从子类覆盖父函数。

不过,我需要这样的功能。这是一个描述我正在尝试做的非功能设置。

class Parent 
public:
  Graph createGraph() 
    return new Graph([this](float x) 
        return this->getY(x); 
    );
  

  float getY(float x) 
    return sin(x);
  

.

class Child: public Parent 
public:
  float getY(float x) 
    return x * x;
  

我的设置的重要部分是我有一个父类,它有一个始终引用一个经常被子类重载的函数的函数。来自 Java/javascript 领域,我的方法是执行您在上面看到的操作,但似乎我对 c++ 的考虑不正确。

我如何模拟(即获得相对相似的功能)这种形式的覆盖?

我知道,如果不是 DRY,我可以将 createGraph 复制/粘贴到两者中,它会起作用。如果这是一个有经验的 c++ 开发人员会这样做的方式,那对我来说已经足够了。但现在,我正在寻找一种方法来解决这个问题,这种方法与我的更像 java 的方法一样 DRY。

编辑:这里的核心问题似乎是我误解了virtual 所做的事情,假设这意味着父类中的函数可能没有定义(类似于抽象函数其他语言)。事实并非如此,virtual 似乎做了其他允许抽象类的事情,但不是必需的。

【问题讨论】:

您要查找的内容不在您提供的链接中?您的选择是使用 virtual 并覆盖,或者不使用并隐藏。如果这是您所要求的,则没有第三种选择。您对关键字 virtual 有什么看法吗?此外,您无需输入“this” 在您可能不熟悉的其他语言中,还有第三个选项。它在功能上很有用,但不是迄今为止我所展示的语法的选项。在使用“this”方面,我只想具体一点。我知道它没有任何改变,但它让我更容易阅读。感谢您的意见。 您能解释一下使getY virtual 无法完成您需要的操作吗? 你提到的“其他语言”中的这个“第三个选项”是什么?你可以链接它,所以我知道你在追求什么? similar to abstract functions in other languages >> 在c++中,真正的抽象函数被称为“纯虚函数”,普通的虚函数可以有默认实现。 【参考方案1】:

使用 CRTP 模式。 https://gcc.godbolt.org/z/J_N5Y_

void sink(int);
template<class ChildT>
struct Parent 
    void doStuff()
        sink(
            static_cast<ChildT*>(this)->getY()
            );
    
    int getY() 
        return 42;
    
;
struct Child : Parent<Child> 
    int getY() 
        return 43;
    
;
struct Child2 : Parent<Child2> 
//Does not want to customize, default is fine.
;
void foo() 
    Child c;
    c.doStuff(); # passes 43
    Child2 c2;
    c2.doStuff(); # passes 42

【讨论】:

这是一个非常好的 CRTP 和静态(即编译时)多态性示例,使用 OP 的示例。【参考方案2】:

在父类中,您应该将要覆盖的函数设为虚拟,例如:

 class Parent
 virtual float getY(float x) 
    return sin(x);
  

【讨论】:

如果孩子不覆盖,我希望有默认行为。 @SephReed “我想为此设置默认行为”是什么意思?您应该说明在哪种情况下您希望调用哪个子函数或父函数。 如果你想默认说“父行为”,那么为什么要重写它只是继承原样 @SephReed 您可以拥有虚函数的默认实现。看来(根据您的另一条评论)这是这里误解的核心。 是核心误区。抱歉,virtual 似乎与 abstract 非常不同,虽然经常一起使用。【参考方案3】:

不确定您要查找的内容尚未在您提供的链接中提供,但这是您在 C++ 中实现覆盖的方式。

class Parent

public:
    virtual ~Parent() = default;

    Graph createGraph(float x)
    
        return Graph(getY(x));
    

    virtual float getY(float x) const
    
        return sin(x);
    
;

class Child: public Parent

public:
    float getY(float x) const override
    
        return x * x;
    
;

int main()

    // Play around here with creating a Parent instead of a Child
    //    and/or taking away the virtual keyword to learn how this works
    std::unique_ptr<Parent> thingy = std::make_unique<Child>();
    thingy->createGraph(1.0f);

【讨论】:

啊。看来我误解了virtual。我从未见过定义的虚函数,也没有意识到这是可能的。我试试看。 @SephReed -- 既然您提到了 Java,那么这里的关键是在 Java 中,所有 未标记为“final”的函数都是虚拟的。在 C++ 中,您必须说您希望函数是虚拟的。【参考方案4】:

工作函数如何调用:

struct Parent
  virtual void foo() // Func(1)
  void call()
    this->foo();         // (A) call final overrider
    this->Parent::foo(); // (B) call Parent::foo
    
  ;

struct Derived:Parent
  void foo() override  // Func(2)
  ;

void test()
  Parent parent; //final overrider of foo is Func(1)
  Derived derived1; //final overrider of foo is Func(2)
  Parent& derived2 = derived; //final overrider of foo is Func(2).

  parent.call()//  At (A) call => Func(1)
               //  At (B) call => Func(1)

  derived1.call()  // At (A) call => Func(2)
                   // At (B) call => Func(1)

  derived2.call()  // At (A) call => Func(2)
                   // At (B) call => Func(1)

【讨论】:

以上是关于如何在 C++ 中模拟覆盖父函数(不隐藏)?的主要内容,如果未能解决你的问题,请参考以下文章

C++重载隐藏和覆盖的区别

c++ 虚函数 如何调父类 而非子类

c++ 函数的隐藏和覆盖

C++子类如何调父类的虚函数

如何隐藏 C++ 头文件中的函数

C++学习(四五零)重载覆盖隐藏