如何覆盖作为函数输入的基 class 的方法?

How to override a method of a base class which is an input of a function?

我正在尝试重写代码中的一个方法,我正在使用它来了解这一点,但我没有成功,我错过了什么?

这里有一些代码可以重现我的问题:

my_class.h

#ifndef MY_CLASS_H
#define MY_CLASS_H


class Base {
    public:
        Base();
        virtual ~Base();
        virtual double evaluate(double x);

};

double call_evaluate(Base funct, double x);

#endif

my_class.cpp

Base::Base () {}
Base::~Base () {}
double Base::evaluate(double x){
        std::cout << "evaluate base\n";
    return 0.0;
};

double call_evaluate(Base funct, double x){
    return funct.evaluate(x);
}

main.cpp

#include "my_class.h"
#include "iostream"


class Derived: public Base{
private:
    double value;

public:

    Derived(double value){
        this->value = value;
    }

    double evaluate(double x) override{
        std::cout << "evaluate derived\n";
        return this->value*x;
    }

};


int main(){

    Derived problem(6);
    double a=problem.evaluate(2);
    double b=call_evaluate(problem, 2);
    std::cout << "a = " << a << ", expected = 12\n";
    std::cout << "b = " << b << ", expected = 12\n";

    return 0;
}

Output

evaluate derived
evaluate base
a = 12, expected = 12
b = 0, expected = 12

这是一个多态性问题。 在您的示例中,基础 class 是 parent class,派生 class 是 child class.

A child class 具有从 parent class 继承的属性。因此,由于 属性 的传承。 (注意,反过来是不可能的。)但是,通过将 child class 的 object 传递给母亲 class 的 object,你输了child class.

相关的所有信息

在您的示例中,call_evaluate 函数采用 parent class 中的 object 作为参数。但您正试图从 child class 传递 object。这会丢失 child class 信息。

要解决您的问题,您必须添加对函数参数的引用,例如:

double call_evaluate(Base &funct, double x).

你也可以像这样传递一个指针:

double call_evaluate(Base *funct, double x).

简而言之,您需要更改 call_evaluate 以通过引用接收第一个参数。

double call_evaluate(Base& funct, double x);

有关您的程序如此运行的原因,请参阅下文。

您的编码风格表明您来自 Java-like 背景。您必须让自己意识到关于 C++ 的一个非常重要的观点。在下面声明的函数中,两个参数都是按值传递的,而不是按引用传递的。

double call_evaluate(Base funct, double x);

“按值”在当前的 C++ 说法中意味着将要复制源对象,即,将创建原始对象的未命名副本,然后将其传递给函数。 “通过引用”意味着源对象仅被引用或别名,也就是说,函数接收对同一对象的引用或别名。

从 Java-like 程序员的角度来看,这应该类似于下面的 pseudo-code。

auto tmp0 = funct.clone();
auto tmp1 = x.clone();
call_evaluate(tmp0, tmp1);

在 C++ 中,您可以通过使用 & 声明符将参数类型显式标记为引用类型来实现“通过引用”的效果。

double call_evaluate(Base& funct, double x);

对引用类型进行“通过引用”不需要在调用方进行特殊标记。

call_evaluate(func, x);

此外,在C和C++中,您可以通过将对象地址作为参数传递来实现“通过引用”的效果。为此,您可以使用 * 声明符将参数类型显式标记为指针类型。

double call_evaluate(Base* funct, double x);

使用指针类型“通过引用”调用需要调用者使用一元 & 运算符来获取对象的地址。

call_evaluate(&func, x);

现在,你的程序有什么问题?您正在“切片”一个多态对象。

由于call_evaluate“按值”接收第一个参数,C++ 将生成一个“复制”表达式以从源对象创建一个新的、临时的、未命名的对象。

此“复制”表达式的目标类型将是参数的类型,Base。换句话说,C++ 将生成一个表达式,目的是从源对象创建一个新的、临时的、未命名的 Base 类型的对象。

由于这个临时的、未命名的对象具有类型 Base,您将很容易地看到它的多态行为就像任何 Base 类型应该的那样。

这称为“切片”,因为它“切片”了对象,也就是说,它仅从源对象的 Base 部分创建了一个新对象。

您可能会从这些实验中获益:

声明call_evaluate为纯虚函数。如果baseclass里面没有定义,这个程序会做什么呢?有关详细信息,请参阅 https://en.cppreference.com/w/cpp/language/abstract_class

禁止使用 = delete 特殊语法的 Base 复制构造函数和复制赋值运算符。如果不允许复制Base,编译器会做什么?有关详细信息,请参阅 https://en.cppreference.com/w/cpp/language/function#Deleted_functions