| Lesson 6 |
What is an object? |
| Objective |
Understand how an object relates to a class. |
C++ Objects and Classes (How Objects Relate to Classes Explained)
An object refers to a location in memory and to the initial value in that memory location. In C++ object-oriented programming, the word
object refers to a struct or class variable — that is, a variable of a
user-defined type that has both data values and methods that act on those data values. When an object is created from a class, it is called an
instance[1] of the class. For example, if we have a class
Person that contains a data member
first_name, then a
Person object would have some value (such as "Laura") in the data member
first_name. Lesson 5 defined what a class is and introduced the class declaration syntax. This lesson examines what an object is, how the relationship between class and object maps to the type/variable relationship in procedural languages, and what each object receives when it is instantiated from a class.
What Is an Object in C++?
A Region of Storage with Associated Semantics
In C++, an object is a region of storage with associated semantics. This definition is broader than the OOP-specific meaning: in the C++ object model, even variables of fundamental types are objects. C++ is statically typed — every object has a type that is known at compile time, and the type determines the semantics: what operations are valid on the object, how much storage it occupies, and how its lifetime is managed. The "associated semantics" means that the type defines not just the data layout but also what it means to copy, move, compare, and destroy the object — properties that user-defined types can customize through special member functions.
The int Example — Even Fundamental Types Are Objects
The declaration:
int i;
specifies that the variable i is an object of type int. This declaration creates a named region of storage large enough to hold one int value, with the semantics of integer arithmetic attached. The same definition applies to user-defined types: Tank armoredTank{...}; creates a named region of storage large enough to hold one Tank object, with the semantics defined by the Tank class attached. The C++ object model is uniform — fundamental types and user-defined types follow the same rules for storage, lifetime, and value semantics.
Objects in the OOP Context — Instances of Classes
In the context of the object model of C++, the term object refers to an instance of a class. Thus a class defines the behavior of possibly many objects — each object is one realization of the class blueprint with its own storage and its own state. In the context of the object model of C++, the term object refers to an instance of a class. Objects are usually referred to by references, which are aliases for an object:
The obvious implementation of a reference is as a (constant) pointer that is dereferenced each time it is used.
A C++ reference binds to an existing object and provides an alternative name for it. Unlike a pointer, a reference cannot be null and cannot be reseated to refer to a different object after initialization — properties that make references safer than pointers for most parameter-passing use cases.
Class Defines the Form — Object Is the Instance
The Class Blueprint and the Object Instance
A C++ class definition generates a user-defined type. A class defines the characteristics of its instances in terms of members: data members (state), member functions (methods or operations), and the visibility of these members to other classes. The class defines the form of all objects that belong to that class — it is the blueprint. An object is one concrete realization of that blueprint — it is the instance. If one were to compare this with a procedural language, a class would be the type and an object would be the variable. The class definition itself creates no storage; storage is created only when an object is instantiated.
The Person Example — Class to Object
The following example shows the relationship between the Person class definition and the objects created from it:
#include <string>
#include <iostream>
class Person {
public:
std::string first_name;
std::string last_name;
int age = 0;
void introduce() const {
std::cout << "Hello, my name is " << first_name
<< " " << last_name << ", age " << age << '\n';
}
};
int main() {
// Two objects — two independent instances of the same class
Person laura;
laura.first_name = "Laura";
laura.last_name = "Smith";
laura.age = 28;
Person james;
james.first_name = "James";
james.last_name = "Chen";
james.age = 34;
laura.introduce(); // Hello, my name is Laura Smith, age 28
james.introduce(); // Hello, my name is James Chen, age 34
// laura and james share the introduce() member function
// but each has its own first_name, last_name, and age
}
Multiple Objects — Each Gets Its Own Data Members
Each object of the class that is created receives a copy of all the class data members, except for those declared as static. In the Person example, laura and james each have their own independent first_name, last_name, and age storage. Modifying laura.age does not affect james.age — the two objects are entirely independent. We will be looking at classes and objects in much greater detail throughout this course.
What Each Object Receives
Per-Object Data Members — Independent Copies
When a new object is instantiated from a class, the runtime allocates storage for a complete copy of all non-static data members. Each data member in the new object is independent of the corresponding data member in every other object of the same class. The size of an object in memory is determined by the combined storage requirements of its non-static data members, plus any padding the compiler inserts for alignment. For a Person with two std::string members and one int, each Person object occupies the storage of two strings and one integer — regardless of how many other Person objects exist.
Shared Member Functions — One Copy for All Objects
All objects of a particular class share the member functions for that class. There is exactly one copy of Person::introduce() in the compiled program, regardless of how many Person objects are created. When laura.introduce() is called, the compiler passes a hidden pointer to laura — the this pointer — so that introduce() knows which object's first_name and last_name to print. When james.introduce() is called, the same function executes with this pointing to james. The function code is shared; the data it accesses is per-object.
Static Data Members — The Exception
The rule that each object receives its own copy of all data members has one exception: data members declared
static. A static data member is shared across all objects of the class — there is exactly one copy in the program regardless of how many objects are instantiated. Static data members are useful for class-wide counters, configuration constants, and shared resources:
#include <iostream>
class Tank {
public:
static int object_count; // shared across all Tank objects
Tank() { ++object_count; }
~Tank() { --object_count; }
static int count() { return object_count; }
};
int Tank::object_count = 0; // definition outside the class
int main() {
std::cout << Tank::count() << '\n'; // 0
{
Tank t1, t2, t3;
std::cout << Tank::count() << '\n'; // 3
} // t1, t2, t3 destroyed here
std::cout << Tank::count() << '\n'; // 0
}
In a tank game,
Tank::object_count tracks how many tank objects are alive at any point during gameplay — one shared counter updated by every constructor and destructor call, accessible through any
Tank object or through the class name directly (
Tank::count()).
Object Lifetime — Storage Duration in C++
Automatic Storage — Stack Objects
The most common object lifetime in C++ is automatic storage duration — objects declared inside a function or block. Automatic objects are created when their declaration is reached during execution and destroyed automatically when execution leaves the scope that contains the declaration. The compiler manages all allocation and deallocation; no explicit cleanup code is required. Most local variables in C++ programs are automatic objects. In a game loop, a temporary Vector3 created inside a physics calculation function is an automatic object — it exists for the duration of the function call and is destroyed when the function returns.
Static Storage — Global and Function-Static
Objects with static storage duration exist for the entire lifetime of the program. Global variables and variables declared with the static keyword inside a function have static storage duration. They are initialized before main() begins and destroyed after main() returns. Static local variables are initialized the first time their declaration is reached and persist between function calls — a pattern used for lazy initialization of expensive resources. In game development, a global configuration object or a static resource cache typically has static storage duration.
Dynamic Storage — Heap Objects and Smart Pointers
Objects with dynamic storage duration are created explicitly by the programmer and exist until explicitly destroyed. In C++, dynamic allocation uses new and delete, but direct use of these operators is discouraged in modern C++ — smart pointers manage dynamic objects automatically. std::unique_ptr<Tank> owns a dynamically allocated Tank object and destroys it automatically when the unique_ptr goes out of scope. std::shared_ptr<Tank> allows shared ownership — the Tank is destroyed when the last shared_ptr referencing it is destroyed. Dynamic storage is appropriate when the lifetime of an object must extend beyond the scope that creates it, or when the number of objects is not known at compile time.
The this Pointer — Connecting Member Functions to Objects
Every non-static member function receives a hidden parameter — the
this pointer — which points to the specific object on which the function was called. When
laura.introduce() executes,
this points to
laura. When
james.introduce() executes,
this points to
james. Inside the function body, every reference to a data member is implicitly qualified by
this — writing
first_name inside
introduce() is equivalent to writing
this->first_name. The
this pointer is what makes one copy of a member function capable of operating on any number of objects — it is the mechanism by which the function knows which object's data to access.
void Person::introduce() const {
// Implicit: this points to the object on which introduce() was called
std::cout << this->first_name << ' ' << this->last_name << '\n';
// Equivalent — this-> is implicit when unambiguous:
std::cout << first_name << ' ' << last_name << '\n';
}
Objects in the Game Development Context
One Class, Many Instances
In World of Tanks, Call to Arms: Panzer Elite, and WWII Tanks: Battlefield, a single Tank class definition serves as the blueprint for every tank object that exists during a match. A battle might involve sixty tank objects — sixty independent instances of the Tank class, each with its own position, health points, ammunition count, turret angle, and crew state (per-object data members), but all sharing the same movement physics, targeting logic, and damage calculation code (member functions). When a shell impacts a specific tank, the damage function executes with this pointing to that specific tank object — modifying only its health, not any other tank's. The static member counter from the earlier example tells the game engine exactly how many tank objects are alive at any frame, enabling resource management and victory condition checks. The class/object relationship — one blueprint, many independent instances — is the foundational concept that makes this architecture possible.
[1] instance: An object is an instance of a class, which was made using a specific class. Hence, 'object' and 'instance' are the same thing. However, the word 'instance' indicates the relationship of an object to its class.
In the next lesson, you will learn how to declare member functions — the syntax for defining the behavior that member functions provide.

