簡體   English   中英

覆蓋虛擬成員函數時,為什么覆蓋函數總是變為虛擬?

[英]When overriding a virtual member function, why does the overriding function always become virtual?

我寫這樣的時候:

class A {
    public: virtual void foo() = 0;
}

class B {
    public: void foo() {}
}

... B :: foo()也變為虛擬。 這背后的理由是什么? 我希望它的行為類似於Java中的final關鍵字。

補充:我知道這樣的工作方式和vtable如何工作:)問題是,為什么C ++標准委員會沒有留下開放直接調用B :: foo()並避免vtable查找。

該標准確實留下了一個直接調用B :: foo的開口並避免了表查找:

#include <iostream>

class A {
    public: virtual void foo() = 0;
};

class B : public A {
    public: void foo() {
        std::cout <<"B::foo\n";
    }
};

class C : public B {
    public: void foo() {
        std::cout <<"C::foo\n";
    }
};

int main() {
    C c;
    A *ap = &c;
    // virtual call to foo
    ap->foo();
    // virtual call to foo
    static_cast<B*>(ap)->foo();
    // non-virtual call to B::foo
    static_cast<B*>(ap)->B::foo();
}

輸出:

C::foo
C::foo
B::foo

因此,您可以獲得您所期望的行為,如下所示:

class A {
    virtual void foo() = 0;
    // makes a virtual call to foo
    public: void bar() { foo(); }
};

class B : public A {
    void foo() {
        std::cout <<"B::foo\n";
    }
    // makes a non-virtual call to B::foo
    public: void bar() { B::foo(); }
};

現在調用者應該使用bar而不是foo。 如果他們有一個C *,那么他們可以將它轉換為A *,在這種情況下, bar將調用C::foo ,或者他們可以將其轉換為B *,在這種情況下bar將調用B::foo 如果它想要,C可以再次覆蓋bar,否則不會打擾,在這種情況下,調用C *上的bar()調用B::foo()如你所料。

但我不知道何時會有人想要這種行為。 虛函數的重點是為給定對象調用相同的函數,無論您使用的是什么基類或派生類指針。 因此,C ++假定如果通過基類對特定成員函數的調用是虛擬的,那么通過派生類的調用也應該是虛擬的。

聲明virtual方法時,基本上是在vtable中添加新條目。 覆蓋virtual方法會更改該條目的值; 它不會刪除它。 對於像Java或C#這樣的語言來說,這基本上也是如此。 不同之處在於,使用Java中的final關鍵字,您可以要求編譯器任意強制執行無法覆蓋它。 C ++不提供此語言功能。

僅僅因為該類被強制使用vtable,並不意味着編譯器被迫使用它。 如果對象的類型是靜態已知的,則編譯器可以自由地繞過vtable作為優化。 例如,在這種情況下可能會直接調用B :: foo:

B b;
b.foo();

不幸的是,我知道驗證這一點的唯一方法是查看生成的匯編代碼。

因為從技術上講,無論你做什么,它都是虛擬的 - 它在表格中占有一席之地。 其余的將是一個語法執法,這是C ++與java不同的地方。

在定義第一個虛函數時,為基類創建一個vtable。 在您的示例中,foo()在vtable中有一個條目。 當派生類繼承自基類時,它也繼承了vtable。 派生類必須在其vtable中具有foo()的條目,以便在通過基類指針以多態方式引用派生類時,將適當地重定向調用。

似乎至少Visual Studio能夠利用final關鍵字來跳過vtable查找,例如這段代碼:

class A {
public:
    virtual void foo() = 0;
};
class B : public A {
public:
    void foo() final {}
};
B original;
B& b = original;
b.foo();
b.B::foo();

b.foo()bB::foo()生成相同的代碼:

        b.foo();
000000013F233AA9  mov         rcx,qword ptr [b]
000000013F233AAE  call        B::foo (013F1B4F48h)
        b.B::foo();
000000013F233AB3  mov         rcx,qword ptr [b]
000000013F233AB8  call        B::foo (013F1B4F48h)

而沒有final它使用查找表:

        b.foo();
000000013F893AA9  mov         rax,qword ptr [b]
000000013F893AAE  mov         rax,qword ptr [rax]
000000013F893AB1  mov         rcx,qword ptr [b]
000000013F893AB6  call        qword ptr [rax]
        b.B::foo();
000000013F893AB8  mov         rcx,qword ptr [b]
000000013F893ABD  call        B::foo (013F814F48h)

不過,我不知道其他編譯器是否也這樣做。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM