One Definition Rule (ODR) — to podstawowa zasada C++, która wymaga, aby w obrębie całego programu (wszystkich jednostek tłumaczenia) dla każdego obiektu, funkcji lub klasy istniała dokładnie jedna zdefiniowana implementacja (definicja).
ODR jest naruszane, gdy:
Prowadzi to do trudnych do wyśledzenia, nieprzewidywalnych błędów, niewłaściwego linkowania lub, co gorsza, do różnego zachowania programu w zależności od sposobu jego kompilacji.
Dlaczego dochodzi do naruszeń:
W dużych projektach często kopiowane/modyfikowane są pliki .h bez kontroli wersji lub dzieli się kod na wiele modułów z oddzielną kompilacją. Jeśli ktoś zmienia funkcję inline tylko w jednym miejscu, inne pliki źródłowe mogą mieć starą wersję.
Jak unikać:
constexpr lub, od C++17, inline variables.Czy można definiować static-funkcje (static void foo()) o tych samych nazwach i różnej implementacji w różnych plikach .cpp bez konsekwencji?
Wielu uważa, że static-funkcje w ogóle nie wpływają na siebie między modułami. Odpowiedź: tak, można, ponieważ każda z nich ma wewnętrzny linkage (widoczna tylko w swoim translation unit). Jednak dla funkcji inline i szablonów takie nie jest gwarantowane, co często wprowadza w błąd.
Przykład:
// file1.cpp static void foo() { std::cout << "A"; } // file2.cpp static void foo() { std::cout << "B"; }
Wywołania w tych modułach będą niezależne.
Historia
W dużym projekcie jeden programista zmienił ciało funkcji inline tylko w jednym z klonów pliku .h. Po kompilacji niektóre zachowania stały się nieprzewidywalne: część modułów działała według starego algorytmu, część — według nowego. Powód — naruszenie ODR dla funkcji inline.
Historia
Podczas migracji na C++17 zmienna-stała była definiowana w kilku plikach nagłówkowych bez użycia słowa kluczowego inline. Podczas linkowania pojawiło się wiele błędów duplicate symbol. Naprawiono to, ogłaszając zmienną jako
inline const.
Historia
Późno odkryto, że wygenerowany plik .h systemu kompilacji zawierał dwie implementacje tej samej metody w szablonowej klasie, różniące się jednym sprawdzeniem ifdef w różnych kompilacjach. Później aplikacja okresowo się zawieszała z powodu rozbieżności w ABI między powiązanymi modułami.