One Definition Rule

From K-3D

Jump to: navigation, search

Overview

The One Definition Rule (ODR) mandates that an entity in a C++ program must have one-and-only-one definition. As a trivial example, a program cannot define a function "foo" in file a.cpp:

void foo()
{
  // do something
}

and define it again in file b.cpp:

void foo()
{
  // do something else
}

In this case, linking files a.cpp and b.cpp into a single library or executable will cause a linking error, and the problem can be easily fixed.

Even if files a.cpp and b.cpp are in separate libraries, this error will be caught by the linker if the libraries are statically linked into a single binary.

Where problems arise is when a.cpp and b.cpp are part of shared libraries, which are linked together dynamically at runtime. In this case, the behavior of the runtime linker is effectively undefined. In general, runtime linkers will pick one definition to use (e.g. the first one loaded) and ignore the rest. This leads to undefined behavior (usually a segfault) when code that relies on one definition of foo() executes using the other definition.

Dynamic Typing

Further complicating the picture is the fact that the ODR applies to objects that aren't created directly by the programmer, such as virtual function tables and - of particular importance in K-3D - std::type_info instances.

Normally, the compiler creates an instance of std::type_info for every class it encounters within a translation unit. These std::type_info instances are used to implement the C++ exception-handling mechanism, plus the typeid() and dynamic_cast() operators that are used frequently in K-3D.

This can lead to a very subtle violation of the ODR: it is possible (through forward declarations) to successfully compile a translation unit where the compiler does not have the full declaration of a class, producing a std::type_info with a different definition from one produced with a full class declaration. This will manifest itself as undefined behavior (a segfault) when using the typeid() or dynamic_cast() operators on the given class.

Real World Example

An example of this problem in K-3D occurred in a file that forward-declared a class, then attempted to use the typeid() operator on a class pointer:

class mesh; // Forward (incomplete) declaration

if(property.type() == typeid(mesh*)) // Crash here in typeid()
{
  // do something
}

In this case, the file compiles correctly, because the compiler does not require the entire class declaration to work with a class pointer; however, the std::type_info generated does not match one generated with the full declaration - note that this is not a syntax, compiler, or linker error, it is an example of an ODR violation that only the programmer can identify!

The solution in this case is to ensure that the compiler has the full declaration for the class in question at the point where type_info() or dynamic_cast() are used:

#include <mesh> // Full declaration

if(property.type() == typeid(mesh*)) // Works!
{
  // do something
}

References

See http://en.wikipedia.org/wiki/One_Definition_Rule for details.

Personal tools