编译器如何知道在这种情况下要调用哪个虚拟函数



我正在为考试而学习,我收到了以下代码:

#include <iostream>
#include <string>
#include <cmath>
using namespace std;
class Expression {
public:
Expression() = default;
Expression(const Expression&) = delete;
Expression& operator=(const Expression&) = delete;
virtual ~Expression() {}
virtual double eval()const = 0;
virtual void print(ostream& out)const = 0;
friend ostream& operator<<(ostream& out, const Expression& e) {
// cout << "@";
e.print(out);
return out;
}
};
class BinaryExpression : public Expression {
Expression* _e1, * _e2;
char _sign;
virtual double eval(double d1, double d2)const = 0;
public:
BinaryExpression(Expression* e1, Expression* e2, char sign) : _e1(e1), _e2(e2), _sign(sign) {}
~BinaryExpression() override { delete _e1; delete _e2; }
virtual double eval()const override {
cout << "BE eval" << endl;
return eval(_e1->eval(), _e2->eval());
}
virtual void print(ostream& out)const override {
out << '(' << *_e1 << _sign << *_e2 << ')';
}
};
class Sum : public BinaryExpression {
virtual double eval(double d1, double d2)const override {
cout << "Sum private eval" << endl;
return d1 + d2;
}
public:
Sum(Expression* e1, Expression* e2) : BinaryExpression(e1, e2, '+') {}
};
class Exponent : public BinaryExpression {
virtual double eval(double d1, double d2)const override {
cout << "E private eval" << endl;
return std::pow(d1, d2);
}
public:
Exponent(Expression* e1, Expression* e2) : BinaryExpression(e1, e2, '^') {}
};
class Number : public Expression {
double _d;
public:
Number(double d) : _d(d) {}
virtual double eval()const override {
cout << "Num eval" << endl;
return _d;
}
virtual void print(ostream& out)const override {
out << _d;
}
};
int main() {
Expression* e = new Sum(
new Exponent(
new Number(2),
new Number(3)),
new Number(-2));
cout << *e << " = " << e->eval() << endl;
delete e;
}

我使用调试器来查看执行了哪些行,但我仍然想知道编译器如何知道每次在我们调用e->eval()main()中调用哪个函数

输出:

BE eval
Num eval
BE eval
Num eval
Num eval
E private eval
Sum private eval
((2^3)+-2) = 6

假设每个类都有一个eval函数,其中一些类甚至有2,使用Expression指针让我有点失望。编译器在查找每次运行哪个eval()时,究竟要搜索什么?

您的问题有两个主题:

如果某些类有多个同名函数,编译器如何知道要使用哪个函数?

嗯,它们可能有相同的名称,但它们没有相同的签名eval()eval(x,y)调用之间没有歧义,因为只有一个eval不接受任何参数,只有一个eval接受两个参数。

给定Expression* e,编译器如何知道在e->eval()表达式中调用哪个函数?

答案是编译器不知道。这种情况发生在运行时,而不是编译期间。除非应用一种名为"去机会化"的高级优化技术(这是我不打算在这里谈论的一个大话题(。

通常1当在类上定义虚拟函数时,编译器会在该类型的每个对象中存储额外的数据,即所谓的vtable,它只是一个函数指针数组。然后,当您在虚拟方法上执行e->eval()时,编译器将用两个步骤替换此调用:(1(从与eval虚拟方法对应的e对象中存储的vtable中获取函数指针,(2(用e对象(以及可能的其他参数(调用该函数指针。

1:这是一个实现细节,是可能的策略之一,不一定会发生什么

虚拟功能基于VTable工作。

简而言之,在声明类时,它实际上要求类具有指向实际函数的指针数组。它不是对函数进行常规调用,而是首先从数组中获取此地址,然后调用它

在实践中,虚拟类在引用该数组(VTable(的类中有一个额外的指针,当构建类时,该指针将填充该类的VTable地址(在编译时计算(。

因此,简单的心理模型:它是指向函数的指针,而不是指向数据的指针,背后有一些编译器的魔力

如果你真的愿意,你可以在C中使用函数指针和大量手动代码来模拟这一点。这肯定是一个很好的练习来了解他们。

最新更新