C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
18: Inline Functions
In the previous lessons, we've spent a lot of time breaking logic into functions. That's generally the right move—it makes code readable and reusable. But there's a hidden cost to every function call: the "overhead." The CPU has to push arguments onto the stack, jump to a different memory address, execute the code, and then jump back. In 99% of your code, this cost is invisible. But if you're writing a physics engine or a high-frequency trading app where a function is called ten million times a second, that overhead starts to eat your performance.
The overhead of a simple distance check
Let's look at a concrete example. Imagine we're building a simple 2D game. We need to check the distance between two points constantly to see if a player is within range of an enemy. I'll start by defining a basic Point struct and a function to calculate the squared distance between two of them. (I'm using squared distance because calculating a square root is computationally expensive and usually unnecessary for simple comparisons.)
struct Point {
float x, y;
};
float getDistanceSq(Point p1, Point p2) {
float dx = p1.x - p2.x;
float dy = p1.y - p2.y;
return (dx * dx) + (dy * dy);
}
Now, getDistanceSq is tiny. It's basically two subtractions and two multiplications. The time it takes the CPU to actually jump to this function is almost as long as the time it takes to do the math inside it. This is where inline comes in. By marking a function inline, you're suggesting to the compiler: "Don't make a function call here. Just copy and paste the logic directly into the spot where the function is called."
The trap of the separate compilation unit
Here is where I usually trip up when I'm rushing a project. I'll write my inline function in a .cpp file, thinking I'm keeping my headers clean. Let's see how that looks:
// MathUtils.cpp
#include "MathUtils.h"
inline float getDistanceSq(Point p1, Point p2) {
float dx = p1.x - p2.x;
float dy = p1.y - p2.y;
return (dx * dx) + (dy * dy);
}
I'll compile this, and then I'll try to use getDistanceSq in my main.cpp. I'll get a linker error. Why? Because the compiler needs to see the actual body of the function to "inline" it into the calling code. If the definition is tucked away in a separate .cpp file, the compiler can't see the logic while it's processing main.cpp, so it tries to do a traditional function call. But because I marked it inline, the compiler might not have generated a traditional external symbol for it. The linker then looks for the function and finds... nothing.
Getting the visibility right
To fix this, inline functions almost always need to live in the header file. This allows every file that includes the header to see the implementation and perform the substitution. I'll move the logic into MathUtils.h:
// MathUtils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
struct Point {
float x, y;
};
inline float getDistanceSq(Point p1, Point p2) {
float dx = p1.x - p2.x;
float dy = p1.y - p2.y;
return (dx * dx) + (dy * dy);
}
#endif
One quick side note: you'll notice that if you define a function inside a class definition, C++ treats it as inline by default. You don't need to explicitly type the keyword. I used a standalone function here just to make the mechanics of the inline keyword clear.
Keep in mind that inline is a request, not a command. Modern compilers are incredibly smart; if your function is too complex (like containing a massive loop or recursion), the compiler will ignore your inline request and treat it as a regular function. Conversely, if the compiler sees a tiny function that isn't marked inline, it might decide to inline it anyway. Use it as a hint for your most critical, smallest functions, but don't rely on it as a magic "make fast" button.
📋 Practical Task
Build a High-Performance Bounding Box Collider
You are tasked with creating a simple collision detection utility for a 2D game. To ensure the physics loop runs as fast as possible, you need to implement an inline check to see if a point is inside a rectangular bounding box.
Requirements:
- Create a
Rectstruct withfloat left, top, right, bottom. - Implement an
inlinefunction calledisPointInRectthat takes aPoint(from the previous lesson) and aRect. - The function should return
trueif the point's x-coordinate is betweenleftandright, and its y-coordinate is betweentopandbottom. - Ensure the function is defined in a way that it is visible to any file including your header (avoid the linker error mentioned in the lesson).
- In your
mainfunction, create aRectand aPoint, then call your function and print the result to the console.
There are no comments for now.