2012-08-27 13 views
18

Utworzono abstrakcyjną klasę bazową, która ma czysto wirtualną metodę z domyślnym argumentem.Dobra praktyka: Domyślne argumenty dla czystej wirtualnej metody

class Base { 
    ... 
    virtual someMethod(const SomeStruct& t = 0) = 0; 
    ... 
} 

class Derived : public Base { 
    ... 
    virtual someMethod(const SomeStruct& t = 0); 
    ... 
} 

Chciałbym wiedzieć, czy dobrą praktyką jest ustawienie domyślnego argumentu na czysto wirtualne i ogólnie na wirtualne metody?

+3

Myślę, że miałeś na myśli const SomeStruct * t = 0? – marcinj

+4

@luskan: 'SomeStruct' może być niejawnie wymienialny z' 0'. –

+0

Co masz na myśli przez "ustaw domyślny argument na czysty wirtualny"? –

Odpowiedz

13

często chcą korzystać zarówno domyślne parametry i funkcje wirtualne, jak to zrobić. Inni słusznie zauważyli jednak, że prowadzi to do niejednoznaczności i ogólnie nie jest dobrym pomysłem. Jest to dość proste rozwiązanie, z którego korzystam. Daj swojej wirtualnej funkcji inną nazwę, uczyń ją chronioną, a następnie dostarcz publiczną funkcję z domyślnymi parametrami, które ją nazywają.

class Base { 
protected: 
    virtual void vSomeMethod(const SomeStruct& t) = 0; 
public: 
    void someMethod(const SomeStruc& t = 0) 
    { vSomeMethod(t); } 
} 

Klasy pochodne prostu zastąpić vSomeMethod i nie martwić się w ogóle na temat parametrów domyślnych.

+4

Zamiast wprowadzać inną nazwę "vSomeMethod", dlaczego nie po prostu użyć przeciążenia? To znaczy. dodać publiczną nie-wirtualną funkcję członka 'void someMethod()' na 'Base', która wywołuje' someMethod (0); '. W bardziej ogólnym przypadku, gdy chcesz mieć funkcję wirtualnego elementu 'f (xn, ..., x1, x0)', która może być wywołana z domyślnymi wartościami 'n + 1',' xn = vn, ..., x1 = v1, x0 = v0', przeciążenie wirtualne 'f' może nie mieć wartości domyślnych i może istnieć przeciążenie inne niż wirtualne z domyślnymi parametrami' n' 'f (xn = vn, ..., x1 = v1) 'który wywołuje' f (xn, ..., x1, v0) '. – Ose

+2

@Ose Powodem użycia osobno nazwanej funkcji implementacji zamiast przeciążania tej samej nazwy jest to, że gdy nadpisujesz implementację wirtualną w klasie potomnej, ukrywasz nie-wirtualnego pomocnika w obiekcie nadrzędnym, wymagając dodatkowo "użycia" tej metody. do dziecka. –

6

Nie używać domyślnych parametrów w ogóle, jeśli to możliwe, ale jeśli nie, nigdy ich (see the text for details)

kupisz zarówno Efektywne C++ książek Scott Meyers przedefiniować. Nie pożałujesz tego.

+0

Ustawiam te same wartości domyślne w obu metodach, myślę, że twoja referencja dotyczy sytuacji ambiguouse, gdy różne wartości mogą być ustawione jako domyślne argumenty. – deimus

+0

deimus, to nie ma znaczenia. Nawet jeśli you_to myśl_ użyjesz zawsze tych samych wartości co domyślne argumenty, łatwo jest zrobić literówkę lub zapomnieć o domyślnym argumencie. A debugowanie tego rodzaju błędu sprawi, że twoje życie będzie naprawdę nieszczęśliwe z każdą odpowiednią bazą kodu. –

+2

@deimus nie można zagwarantować, że ktoś w przyszłości nie odziedziczy po klasie bazowej i zmieni domyślną wartość. – juanchopanza

27

W rzeczywistości twój kod jest jednym z najgorszych możliwych wzorców użycia dla parametrów domyślnych, ponieważ obejmuje zarówno dziedziczenie, jak i zachowanie polimorficzne. Popieram poradę, aby zapoznać się z podobną podpowiedzią Scotta Meyersa, ale tutaj jest krótki przegląd:

W przypadku połączeń polimorficznych, parametry domyślne są stosowane zgodnie z deklaracją dla typu statycznego, a nie dynamicznego. Jest to logiczne, ponieważ czas wykonywania nie ma pojęcia o parametrach domyślnych, ale łamie wszelkie rozsądne założenia dotyczące zachowania polimorficznego. Na przykład,

#include <cstdio> 

class Base 
{ 
     public: 
       virtual void f(int a = 1) 
       { 
         printf("Base::f(%d)\n", a); 
       } 
}; 

class Deriv : public Base 
{ 
     public: 
       virtual void f(int a = 2) 
       { 
         printf("Deriv::f(%d)\n", a); 
       } 
}; 

int main() 
{ 
     Base* a = new Deriv(); 
     a->f(); 
     delete a; 
     return 0; 
} 

plony:

Deriv::f(1) 
+0

Dobry przykład, który pokazuje pułapki związane z tym podejściem. –

+1

Twój przykład rozwiał mój umysł, +1 – asimes

0

bym:

  • określenie funkcji wirtualnej z parametrem (bez wartości domyślnej)
  • określenie funkcji nie wirtualne w klasie bazowej, bez podania w wszystkich, które wywołują funkcję wirtualną przechodzi poprawny domyślny parametr

ten sposób klasy pochodne nie muszą się martwić o wartości domyślnej w ogóle.

0

Jeśli chcesz ten kod sensu:

Base* p = new Derived; 
p->someMethod(); 

od statycznego typu p jest Base* to jest podpis bazowy, który jest używany na wezwanie. Wartość domyślna jest przypisana i jako funkcja wirtualna, połączenie jest przekierowywane do pochodnego.

Można nawet mieć je zdefiniowane inaczej, jeśli chcesz, aby Twój Pochodzące :: SomeMethod aby otrzymać inną wartość od Base* zamiast Derived* ...

Ważną rzeczą jest dokumentem te „relacje” dobrze, ponieważ większość programistów nie zrozumie ich z prostej lektury kodu.

Oczywiście, jeśli wszystko, co nie pasuje do konkretnego kontekstu, powoduje więcej zamieszania niż w innych przypadkach, unikaj domyślnych parametrów funkcji wirtualnych i używaj pomocniczego, nie-wirtualnego, aby wywoływać je poprawnie.

Ale biorąc również pod uwagę, że - z punktu widzenia czytelnika - parametr domyślny jest bardziej wytłumaczalny niż funkcja przeciążania, wywołująca prywatnie inną z nieczytelną poprawką parametru.

Powiązane problemy