C++ inline Functions: ODR, Headers, and Compiler Inlining
이 글의 핵심
Practical guide to inline in C++: header-safe definitions, difference from actual inlining, and C++17 inline variables.
What does “inline function” mean?
Historically: “replace the call with the body.” In modern C++, inline is also how you legally define the same function in multiple translation units (ODR).
// Ordinary function (one definition per program)
int add(int a, int b) {
return a + b;
}
// inline function: can live in a header
inline int add_inline(int a, int b) {
return a + b;
}
int main() {
int x = add_inline(3, 4);
// compiler may inline the body: int x = 3 + 4;
}
Why use inline?
inline int square(int x) {
return x * x;
}
for (int i = 0; i < 1000000; i++) {
int result = square(i); // call may be inlined
}
Defining in headers
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
inline int add(int a, int b) {
return a + b;
}
inline int multiply(int a, int b) {
return a * b;
}
#endif
// main.cpp
#include "math_utils.h"
int main() {
int x = add(3, 4);
int y = multiply(5, 6);
}
Class member functions
class Point {
private:
int x, y;
public:
Point(int x, int y) : x(x), y(y) {}
// Defined in class: implicitly inline
int getX() const {
return x;
}
int getY() const {
return y;
}
inline void setX(int newX) {
x = newX;
}
};
// Out-of-line definition still needs inline if in header
inline void Point::setY(int newY) {
y = newY;
}
Practical examples
Example 1: Getters / setters
class Rectangle {
private:
int width, height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
int getWidth() const { return width; }
int getHeight() const { return height; }
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
int area() const { return width * height; }
};
Example 2: Math helpers
inline int abs(int x) {
return x < 0 ? -x : x;
}
inline int max(int a, int b) {
return a > b ? a : b;
}
inline int min(int a, int b) {
return a < b ? a : b;
}
inline int clamp(int value, int low, int high) {
return max(low, min(value, high));
}
int main() {
int x = clamp(150, 0, 100); // 100
std::cout << x << std::endl;
}
Example 3: Small vector type
struct Vec2 {
float x, y;
Vec2(float x = 0, float y = 0) : x(x), y(y) {}
inline Vec2 operator+(const Vec2& other) const {
return Vec2(x + other.x, y + other.y);
}
inline Vec2 operator-(const Vec2& other) const {
return Vec2(x - other.x, y - other.y);
}
inline Vec2 operator*(float scalar) const {
return Vec2(x * scalar, y * scalar);
}
inline float dot(const Vec2& other) const {
return x * other.x + y * other.y;
}
inline float length() const {
return std::sqrt(x * x + y * y);
}
};
int main() {
Vec2 v1(1, 2);
Vec2 v2(3, 4);
Vec2 v3 = v1 + v2;
float d = v1.dot(v2);
float len = v1.length();
}
Example 4: Bit helpers
inline bool getBit(int value, int pos) {
return (value & (1 << pos)) != 0;
}
inline int setBit(int value, int pos) {
return value | (1 << pos);
}
inline int clearBit(int value, int pos) {
return value & ~(1 << pos);
}
inline int toggleBit(int value, int pos) {
return value ^ (1 << pos);
}
int main() {
int flags = 0;
flags = setBit(flags, 0);
flags = setBit(flags, 2);
flags = clearBit(flags, 0);
std::cout << getBit(flags, 2) << std::endl; // 1
}
Limitations
// inline is a hint; compiler may refuse
inline void complexFunction() {
// large body
}
// Often not inlined:
// - deep recursion
// - very large functions
// - virtual calls (usually)
// - calls through function pointers
Common pitfalls
Pitfall 1: Inlining huge functions
// Bad: huge inline
inline void hugeFunction() {
// hundreds of lines
}
// Good: keep small helpers inline
inline int add(int a, int b) {
return a + b;
}
Pitfall 2: Recursive functions
inline int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
// Usually not fully inlined
Pitfall 3: Duplicate definitions in headers
// utils.h
int add(int a, int b) { // ODR violation if included in multiple TUs
return a + b;
}
// Fix: inline
inline int add(int a, int b) {
return a + b;
}
Pitfall 4: Virtual functions
class Base {
public:
virtual inline void func() {
// usually not inlined when called virtually
}
};
Base obj;
obj.func(); // may inline (static type known)
Base* ptr = &obj;
ptr->func(); // usually not inlined
Modern C++: inline
// C++17: inline variable
inline int globalCounter = 0; // OK in header
class MyClass {
public:
inline static int count = 0; // C++17
};
Guidelines
// ✅ Good inline candidates
inline int getValue() const { return value; }
inline int max(int a, int b) { return a > b ? a : b; }
template<typename T>
inline T square(T x) { return x * x; }
// ❌ Often unnecessary
// - huge functions
// - recursion
// - compiler already inlines small hot functions
FAQ
Q1: When to use inline?
A:
- Small hot functions
- Getters/setters
- Header-only definitions
Q2: Performance?
A: Can remove call overhead for tiny functions—measure.
Q3: Is inline mandatory for performance?
A: No—it is a hint; the optimizer decides.
Q4: Header definitions?
A: Non-template functions in headers generally need inline (or templates).
Q5: Compiler auto-inline?
A: Yes, with optimization flags.
Q6: Learning resources?
A:
- Effective C++
- cppreference.com
- C++ Primer
Related posts (internal links)
- RVO/NRVO
- Expression templates
- Profiling
Practical tips
Debugging
- Warnings first
Performance
- Profile first
Code review
- Conventions
Practical checklist
Before coding
- Right approach?
- Maintainable?
- Performance?
While coding
- Warnings?
- Edge cases?
- Errors?
At review
- Intent?
- Tests?
- Docs?
Keywords
C++, inline, optimization, ODR, header
Related posts
- RVO/NRVO
- Alignment & padding
- Branch prediction
- Cache optimization
- Copy elision