ZWI #1: Jak wykryć kontenery asocjacyjne w czasie kompilacji

Tym postem rozpoczynam kolejną serię – ZWI (Zapytane w Internecie). W planach jest co najmniej jeden wpis ;​) Posty będą przedstawiały pytania, na które odpowiedziałem na różnej maści forach, które (lub których odpowiedzi) uznałem za godne uwagi.

Na pierwszy ogień idzie następujące pytanie z forum 4programmers:

Mam prostą funkcję

template <typename Container, typename Value>
bool contains(const Container& container, const Value& value)
{
    return std::find(std::begin(container), std::end(container), value)
        != std::end(container);
}

Dla kontenerów asocjacyjnych (set, multiset, unordered_set, map itd.) można wykorzystać metodę find(), która jest szybsza niż globalne find(). Pytanie czy da się w prosty i elegancki sposób wykryć taką sytuację, żeby wywołać container.find(), bo nie uśmiecha mi się pisać specjalizacji dla każdego typu kontenera.

Lawful Evil #1 – kwadraty magiczne

Rozpoczynam nową serię: Lawful Evil. Jest to znana kategoria postaci z DnD, charakteryzująca się działaniem zgodnym z obowiązującym porządkiem, jednak zaprzeczającym jego duchowi.

W moim wykonaniu jest to wykonywanie zadań (domowych czy innych) zgodnie z literą zadania, ale w sposób taki, aby było jasne, że to parodia/żart. Zadania będą maksymalnie przekombinowane, mogą zawierać celowo rażące błędy i, w ramach możliwości, ich stopień skomplikowania uczyni je niemożliwymi do wytłumaczenia nauczycielowi/wykładowcy.

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ą?