Jeśli chodzi o C++ to mam o sobie całkiem wysokie mniemanie. Niemniej jednak, nie ma nikogo kto by znał ten język w całości. Ostatnio na forum, w dziale Newbie, zobaczyłem pytanie zawierające kod zbliżony do następującego:
// foo.hpp struct foo { void bar(int baz); }; // foo.cpp void foo::bar(int baz = 42) { // ... } |
Moją pierwszą odpowiedzią było “to się nie ma prawa skompilować”. Z tym stwierdzeniem był jednak drobny problem: nie było prawdziwe – oba wiodące kompilatory C++, czyli clang i gcc, kompilowały go bez problemów.
Widząc taki stan rzeczy, uznałem, że należy sprawdzić u źródła: obecnego standardu C++. Po pobieżnej lekturze paragrafu N4140 §8.3.6 [dcl.fct.default] Default arguments dowiedziałem się, że to nie tylko “ma prawo” się skompilować, ale również ma obowiązek.
Co ciekawsze cytaty:
N4140 §8.3.6 [dcl.fct.default] Default arguments/4
For non-template functions, default arguments can be added in later declarations of a function in the
same scope. Declarations in different scopes have completely distinct sets of default arguments
Powyższe tyczy się funkcji nie będących elementami klas.
N4140 §8.3.6 [dcl.fct.default] Default arguments/6
the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition
A to tyczy się już konkretnie przykładu, który zrugałem na forum. Jest nawet okraszone (nienormatywnym) komentarzem z przykładem:
class C { void f(int i = 3); void g(int i, int j = 99); }; void C::f(int i = 3) { // error: default argument already } // specified in class scope void C::g(int i = 88, int j) { // in this translation unit, } // C::g can be called with no argument |
Widząc takie coś na code review raczej bym tego nie przepuścił (przynajmniej nie bez bardzo solidnej argumentacji), ale język jako taki wyraźnie na to pozwala.
A problemem na forum nie były dziwnie umieszczone wartości domyślne.
Mam pytanko, dlaczego tak właściwie ten pierwszy przykład jest “zły”?
Generalnie chodzi o to, że takie definicje czynią kod znacznie mniej przenaszalnym, i zmniejszają jego czytelność. Jeśli wywołasz przeniesiesz z foo.cpp do bar.cpp kod wywołujący foo::bar() bez parametrów, to przestanie się on kompilować. W tak trywialnym przykładzie to jest oczywiste, ale jak masz hierarchię klas, przeładowań itd, to przestaje już takie być.