[英]How does the C++ compiler know which implementation of a virtual function to call?
[英]How does the compiler know which virtual function to call in this situation?
我正在為考試而學習,我得到了這個代碼:
#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()
中調用哪個 function
Output:
BE eval
Num eval
BE eval
Num eval
Num eval
E private eval
Sum private eval
((2^3)+-2) = 6
鑒於每個 class 都有一個 eval function 其中一些甚至有 2 並且使用表達式指針讓我有點失望。 編譯器在尋找每次運行哪個eval()
時究竟搜索什么?
您的問題有兩個主題:
鑒於某些類具有多個同名函數,編譯器如何知道使用哪個 function?
嗯,他們可能有相同的名字,但他們沒有相同的簽名。 eval()
和eval(x,y)
調用之間沒有歧義,因為只有一個eval
不接受任何 arguments,只有一個eval
接受兩個 arguments。
給定
Expression* e
編譯器如何知道在e->eval()
表達式中調用哪個 function ?
答案是編譯器不知道。 這發生在運行時,而不是編譯期間。 除非應用了一種稱為去虛擬化的高級優化技術(這是一個我不打算在這里討論的大話題)。
通常1當在 class 上定義虛函數時,您的編譯器將在該類型的每個 object 中存儲附加數據,即所謂的vtable ,它只是 function 指針的數組。 那么當你在一個虛方法上做e->eval()
時,編譯器會用兩個步驟代替這個調用:(1)從存儲在e
object對應eval
虛方法的vtable中獲取function指針,(2)調用那個function帶有e
object 的指針(以及可能的其他參數)。
1 :這是一個實現細節,可能的策略之一,不一定會發生什么。
虛函數基於VTable工作。
簡而言之,在聲明 class 時,它實際上需要 class 具有指向實際函數的指針數組。 它不是定期調用 function,而是首先從數組中獲取該地址,然后調用它。
在實踐中,虛擬類在 class 中有 1 個額外的指針,該指針指向該數組(VTable),並且當您的 class 被構造時,該指針被填充為該 ZA2F2ED4F8EBC2CBB4C21A29DZ40AB61 的 VTable 的地址(在編譯時計算)。
所以簡單的心理 model:它是指向函數而不是指向數據的指針,背后有一些編譯器魔法。
如果你真的想要,你可以在 C 中使用 function 指針和大量手動代碼來模擬這個。 了解它們肯定是一個很好的練習。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.