Skip to Content
Course content

223: One Definition Rule Explained

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember hitting my first "multiple definition" error early in my career. I had a simple utility function, I put it in a header file so I could use it everywhere, and the moment I included that header in a second .cpp file, the linker absolutely lost its mind. I thought I'd broken the compiler. In reality, I had just run face-first into the One Definition Rule (ODR).

The Linker's Complaint

Let's recreate that frustration. Suppose we're building a small physics engine and we need a utility to calculate the square of a number. I'll start by creating a header called PhysicsUtils.h.

// PhysicsUtils.h
int square(int x) { 
    return x * x; 
}

Now, I'll create two different files to use this function: Gravity.cpp and Collision.cpp. Both will include the header.

// Gravity.cpp
#include "PhysicsUtils.h"
int getGravityForce() { return square(10); }

// Collision.cpp
#include "PhysicsUtils.h"
int getImpactForce() { return square(5); }

When I try to compile these and link them together into one executable, the compiler doesn't complain, but the linker screams: fatal error LNK1169: one or more multiply defined symbols found. It's pointing directly at square().

Here is why: the #include directive is literally a copy-paste mechanism. The compiler creates a translation unit for Gravity.cpp that contains a definition of square(), and another for Collision.cpp that also contains a definition of square(). When the linker tries to smash these together, it sees two identical functions with the same name and has no idea which one to use. The ODR states that for a non-inline function, there can be exactly one definition across the entire program.

Splitting Declaration from Definition

The standard way to fix this is to distinguish between declaring something (telling the compiler it exists) and defining it (telling the compiler how it works). I'll move the logic out of the header and into its own source file.

// PhysicsUtils.h
// This is now just a declaration. 
// "Hey compiler, there's a function called square, you'll find it later."
int square(int x); 

// PhysicsUtils.cpp
#include "PhysicsUtils.h"
// This is the actual definition.
int square(int x) { 
    return x * x; 
}

Now, when I compile, Gravity.cpp and Collision.cpp both know square() exists, but only PhysicsUtils.cpp actually provides the machine code for it. The linker is happy because there is exactly one definition. This is the textbook C++ workflow.

The "Inline" Escape Hatch

But what if the function is tiny? Moving a one-line return to a separate .cpp file feels like overkill and can actually slow down the program because the compiler can't easily optimize (inline) the call. I want the definition back in the header, but I don't want the linker to panic.

I'll try adding the inline keyword:

// PhysicsUtils.h
inline int square(int x) { 
    return x * x; 
}

Suddenly, it compiles and links perfectly. By marking it inline, I'm telling the linker: "You're going to see this definition in multiple translation units, and that's intentional. They are all identical, so just pick one and ignore the rest."

Wait, there's a catch. If I define square() as inline in the header, but then I accidentally provide a different definition of square() in a .cpp file, the behavior is undefined. The linker might not even warn you; it might just pick one version randomly, leading to bugs that are a nightmare to track down. ODR isn't just about the number of definitions; it's about ensuring that if multiple definitions exist (for inlines or templates), they must be identical.

Breaking Things in a Single File

Just for fun, let's see what happens if I violate the ODR within a single file. I don't even need a header for this.

int main() {
    int val = 10;
    int val = 20; // Error!
    return 0;
}

The compiler catches this immediately. You can't define the same variable twice in the same scope. This is the simplest form of the ODR: within a single translation unit, an entity can have only one definition. It's basically the compiler saying, "I can't map the name val to two different memory locations at the same time."




📋 Practical Task

Fixing the Duplicate Logger Definition

You have been handed a small project with a logging utility that is causing linker errors. The current setup violates the One Definition Rule because the logMessage function is defined in a header file and included in multiple source files.

Your Task: Modify the code to resolve the "multiple definition" error. You may choose either the "Declaration/Definition Split" method (creating a .cpp file) or the "Inline" method. Ensure the project links successfully and the output remains the same.

// Logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <iostream>
#include <string>

void logMessage(const std::string& msg) {
    std::cout < "[LOG]: " < msg < std::endl;
}

#endif

// Main.cpp
#include "Logger.h"
int main() {
    logMessage("System starting...");
    return 0;
}

// Network.cpp
#include "Logger.h"
void connectToServer() {
    logMessage("Connecting to server...");
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.