In C++, object oriented programming is defined by direct control over memory and construction efficiency. While languages like Java hide object instances behind references and garbage collection, C++ objects can live directly on the stack or be packed tightly into arrays.
Let's look at encapsulation, the default visibility rules, and how to initialize members without wasting performance.
Class vs Struct: The Only Difference
In C++, the difference between a class and a struct is purely default visibility. That is it. There is no other difference. An object instantiated from a struct can have constructors, virtual functions, and inheritance just like a class.
- class: Members and base classes are
privateby default.
- struct: Members and base classes are
publicby default.
We typically use struct for plain data carriers, and class when we want to enforce encapsulation (protecting internal states using private members).
Encapsulation (Hiding the Wires)
Encapsulation is the practice of keeping data members private and exposing only a clean public interface. This ensures other developers do not modify internal states directly and cause bugs.
- The High Voltage Metaphor: Think of encapsulation like the outer shell of an electrical machine. The dangerous, high voltage wiring is hidden inside (private data members). The user is only exposed to a green start button and a dial (public methods). If you remove the shell and let users touch the wires directly, they will fry the machine (corrupt the object state) and crash the system.
The ABI Layout and the Hidden 'this' Pointer
When you call a member function on an object, how does the function know which object's data to operate on? At the ABI (Application Binary Interface) level, member functions are just regular global functions with a hidden first parameter: the this pointer.
When you call player.heal(50), the compiler secretly rewrites it to Player::heal(&player, 50). On x64 Windows, the CPU literally passes the memory address of player into the rcx register before jumping to the function's instructions. C++ is not magic; it is just very good at hiding the plumbing.
The Constructor Double Initialization Pitfall
When you write a constructor and assign values to members inside the constructor body, you are wasting CPU cycles. Before the code inside the constructor braces runs, C++ automatically initializes all member variables using their default constructors first! When you assign values inside the braces, you overwrite those defaults. This is double initialization.
To avoid this, modern C++ uses Member Initializer Lists (also known as constructor initialization lists). This initializes members directly with their target values, performing only one initialization step:
Member initializers also execute in the order the variables are declared in the class, not the order you write them in the list. Always match your list order to your declaration order to prevent compiler warnings.
The Declaration Order Trap
This ordering rule is not just about clean style; it is a critical safety feature. If you use one member variable to initialize another member variable in the list, and they are declared in the wrong order in the class body, you will initialize the target using uninitialized garbage memory!