Jak przeładowywać operatory w mojej klasie?

Kiedy i które operatory przeładowywać?

Dość przewrotnie odpowiem: kiedy zajdzie taka potrzeba. Podstawą jest zachowanie logiki kodu (Samochod + Samochod nie ma zbyt sensu, ale Currency + Currency już tak) oraz konwencji języka (lub frameworka), np. w przypadku operatora <<.

Wyjście i wejście ze strumienia, czyli operatory << i >>

Tutaj nie ma wielkiej filozofii. Dobrze by było osiągnąć pełną serializację, t.j. zagwarantować poprawność kodu z listingu poniżej, ale w praktyce jest to więcej roboty niż to warte dla większości klas. Szczególnie, że nawet biblioteka standardowa nie zachowuje się w ten sposób, np. dla klasy std::string.

Jak łatwo zaimplementować w C++ operator porównania dla twojej klasy?

Biblioteka standardowa C++ w wielu miejscach oczekuje od typów, z którymi pracuje implementacji operatora< (alternatywą jest podanie własnego komparatora). Jest tak chociażby w std::sort, std::map lub std::binary_serch. Weźmy za przykład następującą klasę:

struct person
{
    string first_name;
    string last_name;
    string city;
    int year_born;
    bool is_male;
};

Aby posortować std::vector<person>, powinniśmy zaimplementować operator<, który spełnia strict weak ordering:

Bezpieczna zamiana jednostek w C++ część 1

sleep(300);

Ile śpimy? 0.3s (300ms)? 5 minut (300s)? A może parametrem powyższego sleep jest jeszcze inna jednostka? Bez zajrzenia do dokumentacji – albo kodu – nie ma na to pytanie odpowiedzi. Akurat w przypadku jednostek czasu C++11 wprowadził zestaw klas odpowiedzialnych za nie, więc można napisać taki zupełnie jednoznaczny kod:

this_thread::sleep_for(chrono::seconds(30));
this_thread::sleep_for(500ms); // C++14

Nie używaj rand(), C++ ma <random>

Dość często spotykam się z kodem wyglądającym w przybliżeniu tak:

int x = rand()%200-100;
int y = rand()%200-100;

Czy zauważyliście już błąd? Jeśli tak lub nie, to możliwe, że nie macie racji – nie da się z powyższego kodu wywnioskować jaka była intencja programisty – czy to miał być kod losujący wartość z przedziału [-100, 99] czy też [-100, 100].

Pomijając nieczytelność/niejawność zamiarów, rand ma jeszcze następujące problemy:

  • nie musi być bezpieczny w wielowątkowości,
  • RAND_MAX może wynosić zaledwie 32767,
  • nawet jeśli sam rand wypluwa wszystkie liczby z jednakowym prawdopodobieństwem, modulo ten porządek zakłóca.

Ciężko nie wspomnieć o słynnym wykładzie “rand() Considered Harmful”: [yt][ch9]

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

Argument vs. parametr – które jest które?

Dość często mi się to myli, jest to więc kolejna doskonała okazja do sprawdzenia, czy zapisanie informacji wspomaga jej retencję.

W kilku słowach: parametr to zmienna/stała będąca częścią prototypu funkcji, a argument to konkretna wartość do niej przekazana.

void foo(int a);
 
int main()
{
    foo(42);
}

W powyższym przykładzie a jest parametrem, a 42 argumentem.

Źródła: 1, 2, 3