Bezpiecznie zastępuj funkcje wirtualne C ++


100

Mam klasę bazową z funkcją wirtualną i chcę przesłonić tę funkcję w klasie pochodnej. Czy istnieje sposób, aby kompilator sprawdzał, czy funkcja, którą zadeklarowałem w klasie pochodnej, faktycznie przesłania funkcję w klasie bazowej? Chciałbym dodać jakieś makro lub coś, co zapewnia, że ​​nie zadeklarowałem przypadkowo nowej funkcji, zamiast nadpisać starą.

Weź ten przykład:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Here parent::handle_event()jest wywoływana zamiast child::handle_event(), ponieważ metoda dziecka pomija constdeklarację i dlatego deklaruje nową metodę. Może to być również literówka w nazwie funkcji lub niewielka różnica w typach parametrów. Może się to również łatwo zdarzyć, jeśli interfejs klasy bazowej zmieni się i gdzieś jakaś klasa pochodna nie zostanie zaktualizowana, aby odzwierciedlić zmianę.

Czy jest jakiś sposób na uniknięcie tego problemu, czy mogę w jakiś sposób powiedzieć kompilatorowi lub innemu narzędziu, aby to sprawdził? Jakieś pomocne flagi kompilatora (najlepiej dla g ++)? Jak uniknąć tych problemów?


2
Świetne pytanie, próbuję dowiedzieć się, dlaczego moja funkcja klasy podrzędnej nie jest wzywana od godziny!
Akash Mankar

Odpowiedzi:


89

Od wersji g ++ 4.7 rozumie nowe overridesłowo kluczowe C ++ 11 :

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};

@hirschhornsalz: Odkryłem, że kiedy implementujesz funkcję handle_event i dodajesz override na końcu implementacji funkcji, g ++ wyświetla błąd; jeśli podasz implementację funkcji wbudowanej w deklaracji klasy po słowie kluczowym override, wszystko jest w porządku. Czemu?
h9uest

3
W definicji overridenależy użyć @ h9uest . Implementacja inline to zarówno definicja, jak i implementacja, więc to jest w porządku.
Gunther Piez,

@hirschhornsalz yeah Otrzymałem ten sam komunikat o błędzie od g ++. Na marginesie: zarówno ty, jak i g ++ error msg używaliście terminu „definicja klasy” - czy nie powinniśmy używać „deklaracji” (para {deklaracja, definicja})? Wyraziłeś się jasno w tym konkretnym kontekście, mówiąc „definicja i implementacja”, ale zastanawiam się tylko, dlaczego społeczność c ++ nagle decyduje się na zmianę warunków w klasach?
h9uest

20

Coś takiego jak overridesłowo kluczowe C # nie jest częścią C ++.

W gcc -Woverloaded-virtualostrzega przed ukrywaniem funkcji wirtualnej klasy bazowej za pomocą funkcji o tej samej nazwie, ale z wystarczająco inną sygnaturą, aby jej nie przesłaniała. Nie ochroni cię to jednak przed niepowodzeniem nadpisania funkcji z powodu błędnej pisowni samej nazwy funkcji.


2
Jest tak, jeśli używasz Visual C ++
Steve Rowe

4
Używanie Visual C ++ nie tworzy overridesłowa kluczowego w C ++; może to jednak oznaczać, że używasz czegoś, co może skompilować jakiś nieprawidłowy kod źródłowy C ++. ;)
CB Bailey

3
Fakt, że zastąpienie jest nieprawidłowe C ++, oznacza, że ​​standard jest zły, a nie Visual C ++
Jon

2
@Jon: OK, teraz widzę, do czego jechałeś. Osobiście mógłbym wziąć lub zostawić overridefunkcjonalność w stylu C # ; Rzadko miałem problemy z nieudanymi zastąpieniami i były one stosunkowo łatwe do zdiagnozowania i naprawienia. Myślę, że nie zgodzę się z tym, że użytkownicy VC ++ powinni go używać. Wolałbym, żeby C ++ wyglądał jak C ++ na wszystkich platformach, nawet jeśli jeden konkretny projekt nie musi być przenośny. Warto zauważyć, że C ++ 0x będzie miał [[base_check]], [[override]]i [[hiding]]atrybutów, dzięki czemu można zdecydować się, by przesłonić sprawdzania w razie potrzeby.
CB Bailey

5
Zabawne, kilka lat po komentarzu przy użyciu VC ++ nie robi overridesłowo kluczowe wydaje się, że tak . Cóż, nie jest to właściwe słowo kluczowe, ale specjalny identyfikator w C ++ 11. Microsoft naciskał wystarczająco mocno, aby zrobić specjalny przypadek i zastosować ogólny format atrybutów i overridewprowadził go do standardu :)
David Rodríguez - dribeas

18

O ile wiem, czy nie możesz po prostu uczynić tego abstrakcyjnym?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Myślałem, że przeczytałem na www.parashift.com, że faktycznie można zastosować metodę abstrakcyjną. Co dla mnie osobiście ma sens, jedyne, co robi, to wymusza na podklasach implementację, nikt nie powiedział nic o tym, że nie może mieć samej implementacji.


Dopiero teraz zdałem sobie sprawę, że to działa nie tylko na destruktory! Świetne znalezisko.
strager

1
Ma to kilka potencjalnych wad: 1) Inną rzeczą polegającą na tym, że oznaczenie jednej lub więcej metod jako abstrakcyjnych, jest to, że klasa bazowa nie jest instancyjna, co może być problemem, jeśli nie jest to częścią zamierzonego użycia klasy. 2) modyfikacja klasy bazowej może nie być Twoja.
Michael Burr

3
Zgadzam się z Michaelem Burrem. Tworzenie abstrakcji klasy bazowej nie jest częścią pytania. Jest całkowicie uzasadnione posiadanie klasy bazowej z funkcjonalnością w metodzie wirtualnej, którą ma zastąpić klasa pochodna. Równie rozsądne jest zabezpieczenie przed zmianą nazwy funkcji w klasie bazowej przez innego programistę i spowodowanie, że klasa pochodna nie będzie już jej zastępować. Rozszerzenie „override” firmy Microsoft jest w tym przypadku nieocenione. Bardzo chciałbym, aby został dodany do standardu, ponieważ niestety bez niego nie ma dobrego sposobu.
Brian

Zapobiega to również BaseClass::method()wywołaniu metody podstawowej (powiedzmy ) w implementacji pochodnej (powiedzmy DerivedClass::method()), np. Dla wartości domyślnej.
Narcolessico,

11

W MSVC możesz użyć overridesłowa kluczowego CLR, nawet jeśli nie kompilujesz dla CLR.

W przypadku g ++ nie ma bezpośredniego sposobu na wymuszenie tego we wszystkich przypadkach; inni ludzie udzielili dobrych odpowiedzi na temat tego, jak wychwycić różnice w sygnaturach za pomocą -Woverloaded-virtual. W przyszłej wersji ktoś mógłby dodać składnię taką jak __attribute__ ((override))lub jej odpowiednik, używając składni C ++ 0x.


9

W MSVC ++ możesz użyć słowa kluczowegooverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override działa zarówno dla kodu natywnego, jak i CLR w MSVC ++.


5

Uczyń funkcję abstrakcyjną, tak aby klasy pochodne nie miały innego wyboru, jak tylko ją przesłonić.

@Ray Twój kod jest nieprawidłowy.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Funkcje abstrakcyjne nie mogą mieć obiektów zdefiniowanych w tekście. Musi zostać zmodyfikowany, aby stał się

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }

3

Sugerowałbym niewielką zmianę w twojej logice. To może działać lub nie, w zależności od tego, co musisz osiągnąć.

handle_event () może nadal wykonywać „nudny kod domyślny”, ale zamiast być wirtualnym, w miejscu, w którym chcesz, aby wykonał „nowy ekscytujący kod”, wywołaj metodę bazową metodą abstrakcyjną (tzn. należy ją przesłonić) które zostaną dostarczone przez twoją klasę potomną.

EDYCJA: A jeśli później zdecydujesz, że niektóre z Twoich klas potomnych nie muszą dostarczać „nowego ekscytującego kodu”, możesz zmienić abstrakcję na wirtualną i podać pustą implementację klasy bazowej tej „wstawionej” funkcjonalności.


2

Twój kompilator może mieć ostrzeżenie, które może wygenerować, jeśli funkcja klasy bazowej zostanie ukryta. Jeśli tak, włącz go. Spowoduje to wykrycie konfliktów const i różnic w listach parametrów. Niestety to nie ujawni błędu pisowni.

Na przykład jest to ostrzeżenie C4263 w programie Microsoft Visual C ++.


1

Słowo overridekluczowe C ++ 11 użyte z deklaracją funkcji wewnątrz klasy pochodnej zmusza kompilator do sprawdzenia, czy zadeklarowana funkcja faktycznie przesłania jakąś funkcję klasy bazowej. W przeciwnym razie kompilator zgłosi błąd.

Dlatego możesz użyć overridespecyfikatora, aby zapewnić dynamiczny polimorfizm (przesłanianie funkcji).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.