2014-05-19 7 views
7

This jest w zasadzie kopią z przykładu podanego w Item 21. Overriding Virtual Functions w książce Herba Suttera Exceptional C++.Co to ma wspólnego z przeciążaniem funkcji?

#include <iostream> 
#include <complex> 
using namespace std; 
class Base 
{ 
public: 
    virtual void f(int); 
    virtual void f(double); 
    virtual ~Base() {}; 
}; 

void Base::f(int) { cout << "Base::f(int)" << endl; } 
void Base::f(double) { cout << "Base::f(double)" << endl; } 

class Derived: public Base { 
public: 
    void f(complex<double>); 
}; 

void Derived::f(complex<double>) { cout << "Derived::f(complex)" << endl; } 

int main() 
{ 
    Base* pb = new Derived; 
    pb->f(1.0); 
    delete pb; 
} 

drukuje kod Base::f(double) i nie mam żadnych problemów z tym. Ale nie mogłem zrozumieć wyjaśnienie podane przez autora na górze strony 122 (podkreślenie moje):

Co ciekawe, mimo że podstawowa * pb jest skierowany do pochodnej obiektu, to nazywa Podstawa: : f (podwójne), , ponieważ rozdzielczość przeciążenia wynosi wykonana na statycznym typie (tutaj Base), a nie na typie dynamicznym (tutaj Pochodny).

Moje zrozumienie jest, że wezwanie pb->f(1.0) to wirtualna rozmowa i Base::f(double) jest ostateczna overrider dla f(double) w Derived. Co to ma wspólnego z przeciążaniem funkcji?

+2

Zmień pb na 'Pochodny *' i obserwuj, co się stanie. – dlf

+0

@dlf: i kompilator podaje również ostrzeżenie :-) – Jarod42

+4

Ten tytuł nie jest zbyt użyteczny. Wyobraź sobie, co myślisz, gdy zobaczysz to w wynikach wyszukiwania. –

Odpowiedz

12

Delikatny częścią jest to, że metody są wirtualne mechanizm wysyłką wywołanie funkcji, a przeciążenie jest cecha, która wpływa na rozdzielczość na wezwanie .

Oznacza to, że przy każdym wywołaniu kompilator musi ustalić, która metoda powinna zostać wywołana (rozwiązać problem); następnie, w logicznie odrębnej operacji, musi wygenerować kod, który wywoła poprawną implementację tej metody (wyślij ją).

Z definicji Base i Derived podanymi powyżej możemy łatwo rozumieć, że jeśli f(double) nazywa na Base* wtedy połączenie powinno być wysłane do dowolnego ręcznym pochodzącym (jeśli dotyczy) zamiast do realizacji podstawowej. Ale odpowiadając, że jest odpowiedzi na pytanie zupełnie inny niż

Gdy źródło mówi pb->f(1.0), która z metod wymienionych f powinny być wykorzystywane do rozwiązywania wywołanie metody?

Jak wyjaśnia Sutter, spec mówi, że po rozwiązaniu połączenia kompilator będzie przyjrzeć metod wymienionych f oświadczył typu statycznego wskazywanego przez pb; w tym przypadku typ statyczny to Base*, więc przeciążenia (nie przesłonięcia!) zadeklarowane na Derived nie będą w ogóle rozpatrywane. Jednak jeśli metoda, do której odwołuje się wywołanie, to virtual, wówczas możliwa implementacja podana na Derived zostanie użyta zgodnie z oczekiwaniami.

+3

Naprawdę tak musi być - możesz nie mieć kodu źródłowego dla deklaracji rzeczywistej pochodnej funkcji wirtualnej, i rzeczywiście ta nadrzędna funkcja mogła nie być jeszcze napisana, więc (a) nie możesz jej uzyskać domyślnie i (b) nawet gdybyś mógł, byłoby to bardzo zaskakujące, gdyby twoja domyślność zmieniała się dynamicznie w czasie wykonywania na wartości, których nigdy nie mógłbyś przetestować. - BTW jedną rzeczą, którą poprawiłbym w stosunku do tekstu, który cytowałeś ode mnie, jest to, że bardziej poprawne jest mówienie o "wyszukiwaniu nazw", niż "rezolucję przeciążenia", ale analiza jest prawidłowa, a wynik taki sam. –

3

Powodem tego przykładem jest ciekawy dlatego, jeśli pb były Derived* zamiast Base* lub jeśli kompilator mógłby jakoś użyć dynamicznego typu zamiast statycznego typu za wykonanie uchwały przeciążeniem, byłoby dopasować wezwanie do pb->f(1.0) do void Derived::f(complex<double>) (complex<double> można domyślnie skonstruować z double). Wynika to z faktu, że obecność funkcji o nazwie f w klasie pochodnej skutecznie ukrywa każdą klasę bazową przeciążającą o tej samej nazwie, nawet jeśli ich listy argumentów są różne.Ale ponieważ statyczny typ pb jest rzeczywiście Base*, tak się nie stanie.

0

W tym przykładzie, pomimo powtarzającego się wystąpienia virtual, nie ma żadnej metody nadpisania w ogóle; metoda f w klasie pochodnej zastępuje żadnego z tych w klasie bazowej, ponieważ typy argumentów nie są zgodne. Biorąc pod uwagę tę okoliczność, nie ma sposobu, aby wywołanie pb->f mogło kiedykolwiek wywołać (unikalną) metodę Derived::f. Rezolucja przeciążania/wyszukiwanie nazw (które uwzględnia tylko metody typu statycznego pb->f) musi wybrać pomiędzy dwiema metodami zadeklarowanymi jako Base::f, aw przykładzie wybierze tę z argumentem typu double. (W czasie działania może zakończyć się wywołanie ręczne, jeśli jest zdefiniowana w różne grupy pochodzącej od Derived i jeżeli przykład zmodyfikowany tak, że pb mógłby wskazywać na przedmiot tego innej klasy pochodnej).

Odrębnym problemem jest to, że metody o nazwie f w Base i Derived nie byłyby jednocześnie brane pod uwagę przy rozdzielczości przeciążenia, jeśli f jest wywoływane z wyrażenia (statycznego) typu Derived, tym razem ponieważ metody w klasie bazowej są ukryte przez deklaracja f w Derived, więc nie są one dostępne dla takich połączeń. Myślę, że tego ukrywania można uniknąć, deklarując using Base::f;, które "podnoszą" metody Base::f do Derived tak, jakby były również zadeklarowane tam (ale przyznaję, nie znając szczegółów tego mechanizmu, przypuszczam, że windy byłyby wtedy przesłonami wirtualnego metoda bazowa z tym samym typem argumentu, ale robi to niewiele, ponieważ windy i tak odwołują się do implementacji w klasie bazowej.)

Powiązane problemy