programowanieInżynier C++

Wyjaśnij, jak działają przeciążanie i wartości domyślne dla funkcji członkowskich klas w C++. Jakie są subtelności dotyczące wspólnego użycia przeciążonych funkcji i wartości domyślnych?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Przeciążenie funkcji (overloading) to mechanizm deklarowania kilku funkcji o tej samej nazwie, ale o różnych sygnaturach (liczbie lub typie parametrów).

Wartości domyślne dla parametrów można określać w deklaracji funkcji. Subtelność: wartości domyślne są brane pod uwagę tylko w momencie kompilacji wywołania i podstawiane przez kompilator, na podstawie widocznej sygnatury.

W klasach często spotyka się sytuację:

class Printer { void print(int n, char c = '*') { /* ... */ } void print(const std::string& s) { /* ... */ } }; Printer p; p.print(5); // wywoła print(int n, char c = '*'), c = '*' p.print("Hi"); // wywoła print(const std::string&)

Subtelności:

  • Należy unikać jednoczesnego przeciążania funkcji i różnicowania ich tylko przez wartości domyślne, co prowadzi do niejednoznaczności.
void foo(int x, int y = 10); void foo(int x); foo(1); // Błąd: niejasne, którą funkcję wywołać

Pytanie z pułapką.

Które z miejsc jest jedynym, gdzie dozwolone jest określenie wartości domyślnej dla funkcji członkowskiej klasy?

Odpowiedź: Wartość domyślna dla metody klasy może być określona w deklaracji metody wewnątrz klasy (lub w pierwszej deklaracji na zewnątrz klasy), ale nie jest dozwolone podawanie jej zarówno w klasie, jak i w implementacji. W przeciwnym razie wystąpi błąd duplikacji definicji.

class X { void func(int x = 5); // Można tutaj }; void X::func(int x) { /* ... */ } // Ale nie tutaj!

Przykłady rzeczywistych błędów z powodu nieznajomości subtelności tematu.


Historia 1

W oprogramowaniu bankowym wystąpił błąd na etapie kompilacji: część przeciążonych funkcji z różnymi wartościami domyślnymi utrudniała jednoznaczny wybór potrzebnej, wywołania były niejednoznaczne na etapie kompilacji — rezultat: niemożność skompilowania wersji do czasu wprowadzenia ręcznych poprawek.


Historia 2

W zespole panował styl, w którym wszystkie wartości domyślne były przenoszone do implementacji, a nie do nagłówka klasy, co jednak dla niektórych metod klas powodowało niespójność między interfejsem a implementacją — różne jednostki TU widziały różne parametry funkcji, co prowadziło do dziwnych błędów kompilacji i błędów wykonania.


Historia 3

Przy rozszerzaniu publicznej biblioteki omyłkowo dodano przeciążenie funkcji z tymi samymi parametrami, ale różnymi wartościami domyślnymi. Kompilator zaczął generować niejednoznaczność podczas wywołań API, a użytkownicy napotkali przestarzałe wywołania i uszkodzone zgodności binarne.