C++ gotcha: funkcje wirtualne i argumenty domyślne

Weźmy za przykład następujące klasy:

struct base
{
	virtual void foo(int value = 42)
	{
		cout << __PRETTY_FUNCTION__ << ": " << value << endl;
	}
};
 
struct derived : base
{
	virtual void foo(int value = 100) override
	{
		cout << __PRETTY_FUNCTION__ << ": " << value << endl;
	}
};

Nic niezwykłego, typowy dynamiczny polimorfizm. Każdy znający podstawy C++ powinien zdawać sobie sprawę, że w poniższym kodzie dwukrotnie zostanie wywołana funkcja derived::foo:

derived obj;
base* b = &obj;
derived* d = &obj;
b->foo();
d->foo();

Zaskakujące może jednak być faktyczne wyjście:

virtual void derived::foo(int): 42
virtual void derived::foo(int): 100

Dlaczego wywołania, które powinny być identyczne jednak takie nie są?

Argumenty domyślne w dziwnych miejscach – czyli również od nowicjuszy można się czegoś nauczyć

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ć”.

Prosty widok na macierz 2D w C++

Dziś tak bardziej o C++. Dość często widzę kod mający w zamierzeniu obsługiwać macierze wyglądający mniej więcej tak:

int** matrix = new int*[h];
for(int i = 0; i < h; ++i) {
    matrix[i] = new int[w];
}

Pomijając nawet kwestię wydajności (to taka macierz JP na 100%, gdzie P to Prefetch), jest to okropny kod. Korzystanie z nagiego new i delete to antyidiom w C++ (szczególnie delete, szerzej opisałem to n.p. tutaj). Jest to:

  • niewygodne: kłopotliwa inicjalizacja, kolejność rząd/kolumna wymaga sprawdzenia sposobu inicjalizacji,
  • niebezpieczne: brak RAII, trzeba pamiętać o odpowiednim zwolnieniu zasobów, można niechcący nadpisać wskaźnik jednego rzędu innym rzędem,
  • niezgrabne: nie ma żadnego mechanizmu upewniającego, że wszystkie wiersze (lub kolumny) macierzy będą tej samej długości.

printf debugging – czytelne i wygodne wypisywanie zmiennych

Uprzedzając krytykę: słyszałem o czymś takim jak debugger. Nie zawsze ma on jednak zastosowanie:

  • debuggery często nie radzą sobie z bardziej zaawansowanymi typami zmiennych (zaczynając ju​ż od C++-owego std::string, o klasach w D nie wspominając),
  • czasem zachodzi potrzeba sprawdzenia większej liczby przebiegów programu lub funkcji. Przechodzenie tego wszystkiego pod debuggerem to nieporozumienie,
  • może nie być w ogóle możliwości użycia debuggera – n.p. w przypadku sprzedaży oprogramowania,
  • wyciąganie armaty (debuggera) na mały problem-muchę również może być znaczącą przesadą.

Z powyższych powodów (ale głównie ostatniego), utworzyłem sobie proste makro w C++ pozwalające czytelnie przedstawiać zawartość zmiennych: