ProgrammatieLead C++ Developer

Vertel over het probleem 'One Definition Rule (ODR)' in C++. Hoe beïnvloedt het grote projecten en hoe kun je overtredingen vermijden?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord

One Definition Rule (ODR) is een fundamentele regel in C++, die vereist dat er voor elk object, functie of klasse in de gehele programma (alle vertaal-eenheden) precies één definitie (implementation) bestaat.

ODR wordt overtreden als:

  • Meerdere implementaties van dezelfde functie/klasse met verschillende code verschijnen in verschillende .cpp-bestanden.
  • Inline-functies of sjablonen verschillende implementaties hebben op verschillende invoerpunt.

Dit leidt tot moeilijk te traceren, onvoorspelbare bugs, onjuiste linking of, nog erger, tot verschillend gedrag van het programma afhankelijk van hoe het is gekoppeld.

Waarom wordt het overtreden:

In grote projecten worden .h-bestanden vaak gekopieerd/modificaties zonder versiecontrole, of de code wordt verdeeld over meerdere modules met afzonderlijke compilatie. Als iemand een inline-functie alleen op één plek wijzigt, kunnen de andere bronbestanden de oude versie hebben.

Hoe te vermijden:

  • Definieer geen functies buiten de klasse in .h-bestanden, tenzij ze inline zijn.
  • Gebruik include guards en controleer de enige plaats van definitie.
  • Gebruik constexpr voor constante variabelen of, vanaf C++17, inline variabelen.

Misleidende Vraag

Mag je static-functies (static void foo()) met dezelfde naam en verschillende implementaties in verschillende .cpp-bestanden zonder gevolgen definiëren?

Veel mensen denken dat static-functies helemaal geen invloed op elkaar hebben tussen modules. Het antwoord is: ja, dat kan, omdat elke functie interne linkage heeft (zichtbaar alleen binnen zijn eigen vertaal-eenheid). Echter, voor inline-functies en sjablonen is dit niet gegarandeerd, wat vaak verwarring oplevert.

Voorbeeld:

// file1.cpp static void foo() { std::cout << "A"; } // file2.cpp static void foo() { std::cout << "B"; }

Oproepen in deze modules zullen onafhankelijk zijn.

Voorbeelden van echte fouten door onbekendheid met de nuances van het onderwerp


Verhaal

In een groot project wijzigde een ontwikkelaar de body van een inline-functie alleen in een van de klonen van het .h-bestand. Na het bouwen werd bepaald gedrag onvoorspelbaar: sommige modules werkten volgens het oude algoritme, andere volgens het nieuwe. De oorzaak was een schending van de ODR voor de inline-functie.



Verhaal

Bij de migratie naar C++17 werd een constante variabele gedefinieerd in meerdere headerbestanden zonder het sleutelwoord inline te gebruiken. Bij het linken ontstonden veel duplicate symbol fouten. Dit werd opgelost door de variabele als inline const te declareren.



Verhaal

Te laat ontdekten we dat het gegenereerde .h-bestand van het buildsysteem twee implementaties van dezelfde methode van een sjabloonklasse bevatte, die verschilde door één ifdef-controle in verschillende builds. Later viel de applicatie sporadisch uit vanwege inconsistentie in ABI tussen de gekoppelde modules.