Classes & Objects

See how a class blueprint becomes real objects with their own data, identity, and behavior — the foundation everything else in C++ OOP builds on.

A class is a user-defined type that groups data and the functions that operate on that data into a single unit. An object is a concrete instance of a class — a piece of storage that holds its own copy of the class's data members and can be manipulated through its member functions. Classes describe what objects of a type look like and how they behave; objects are the actual values a program creates, stores, and works with while it runs.

What Is a Class?

A class is a blueprint, or template, for a type. It specifies what data an object of that type will hold (its data members) and what operations can be performed on that data (its member functions). The class itself does not occupy storage for any particular object's values — it is a description used to create objects.

This declares a class type named Rectangle with two data members, width and height, both accessible from outside the class.

class Rectangle
{
public:
    double width;
    double height;
};

What Is an Object?

An object is an instance of a class type. Creating an object allocates storage for that particular instance's data members, separate from any other object of the same class.

r is an object of type Rectangle. It has its own width and height, independent of any other Rectangle object that might exist.

The class describes the shape of the data; the object is the actual storage holding values for that shape.

Rectangle r;
Class Rectangle (blueprint)
    width  : double
    height : double

r (an object)
+---------+---------+
| width   | height  |
+---------+---------+

Declaring a Class

A class declaration introduces the class's name without yet specifying its members. This is sometimes called a forward declaration.

This tells the compiler that Rectangle is a class type. It is enough to declare a pointer or reference to Rectangle, but not enough to create an object or access members, since the compiler does not yet know what the class contains.

class Rectangle;

Defining a Class

A class definition specifies the complete set of members between an opening brace and a closing brace, and ends with a semicolon.

This defines Rectangle completely: it has two data members and one member function. The closing brace-semicolon is required — omitting the semicolon after a class definition is a syntax error.

class Rectangle
{
public:
    double width;
    double height;

    double area()
    {
        return width * height;
    }
};

Data Members

Data members are variables declared as part of a class. They describe the state that each object of the class will carry.

Every Point object will have its own x and y, initialized or assigned independently of any other Point object.

class Point
{
public:
    double x;
    double y;
};

Member Functions

Member functions are functions declared as part of a class. They express the operations available on objects of that class, and can read and modify the calling object's data members directly, by name, without needing a pointer or reference parameter.

distanceSquared reads x and y directly — they refer to the data members of whichever Point object the function is called on.

class Point
{
public:
    double x;
    double y;

    double distanceSquared()
    {
        return (x * x + y * y);
    }
};

Defining Member Functions Inside vs Outside the Class

A member function can be defined inside the class body, as shown above, or declared inside the class and defined separately outside it using the scope resolution operator ::.

Point::distanceSquared means "the distanceSquared function that belongs to the Point class." Both forms declare the same member function; separating declaration from definition is common when a class is declared in a header file and defined in a source file.

class Point
{
public:
    double x;
    double y;

    double distanceSquared();
};

double Point::distanceSquared()
{
    return (x * x + y * y);
}

Creating Objects

An object is created wherever a variable of the class type is declared. Objects can have automatic storage duration, commonly implemented using the call stack (though the standard does not mandate a particular storage mechanism), or dynamic storage duration when created with new — dynamic object creation is covered in the Dynamic Memory topic.

r1 is default-constructed with no member values specified here. r2 is created using brace (aggregate) initialization, available because Rectangle has no user-declared constructors and only public data members; its width becomes 3.0 and its height becomes 4.0. Classes that define their own constructors are covered in the Constructors & Destructors topic.

Rectangle r1;
Rectangle r2{ 3.0, 4.0 };

Accessing Members with the Dot Operator

The member access operator . is used to access a data member or call a member function on an object.

r.width accesses the width data member. r.area() calls the area member function on r. r.height = 10.0; assigns a new value to r's height data member.

Rectangle r{ 3.0, 4.0 };
std::cout << r.width << '\n';
std::cout << r.area() << '\n';
r.height = 10.0;

Access Specifiers: public, private, and protected

Access specifiers control which parts of a program may access a class's members. public members are accessible from anywhere the object is visible. private members are accessible only from within the class's own member functions (and friends, covered elsewhere). protected members behave like private members for ordinary code but are also accessible to derived classes, covered in the Inheritance topic.

balance is private, so code outside Account cannot write account.balance directly; it must go through the public deposit and getBalance functions. This is the basic mechanism behind encapsulation.

The commented line would fail to compile because balance is not accessible outside the class.

class Account
{
public:
    void deposit(double amount)
    {
        balance += amount;
    }

    double getBalance()
    {
        return balance;
    }

private:
    double balance = 0.0;
};
Account account;
account.deposit(100.0);
std::cout << account.getBalance();
// account.balance = 500.0;   // Error: balance is private

class vs struct

class and struct declare the same kind of user-defined type in C++, with one difference: the default access specifier. Members of a class default to private; members of a struct default to public. Both can have data members, member functions, and access specifiers.

The only structural difference between the two keywords is the default access level.

By convention, many codebases use struct for simple, passive data-holding types with all-public members, and class when private data and a controlled interface are important — but this is a convention, not a language rule.

Aspect                        | class    | struct
Default member access         | private  | public
Default base-class access      | private  | public
Can have member functions      | Yes      | Yes
Can have constructors/destr.   | Yes      | Yes
Can use access specifiers      | Yes      | Yes
struct Point3D
{
    double x;
    double y;
    double z;
};   // members are public by default

class Point3DClass
{
    double x;
    double y;
    double z;
};   // members are private by default

Object Identity

Each object has its own identity: its own storage location, distinct from every other object, even another object of the same class holding identical values.

a and b hold the same width and height, but they are different objects — &a and &b are different addresses, so the comparison prints 0 (false).

Rectangle a{ 2.0, 3.0 };
Rectangle b{ 2.0, 3.0 };
std::cout << (&a == &b);

Object State

An object's state is the current set of values held in its data members at a given moment. State can change over an object's lifetime as its data members are modified.

r's state starts as width 3.0, height 4.0, giving an area of 12. After r.width = 6.0;, r's state has changed, and the same object now reports an area of 24.

r.area() with width 3.0, height 4.0 gives 12 r.width is reassigned to 6.0 r.area() now gives 24, using the object's updated state

Rectangle r{ 3.0, 4.0 };
std::cout << r.area() << '\n';
r.width = 6.0;
std::cout << r.area() << '\n';

Object Behavior

An object's behavior is expressed through its member functions — the operations it exposes for reading or changing its state, or for computing something derived from that state.

increment and value define Counter's behavior. Every Counter object shares the same behavior (the same member function code), but each has its own independent count.

class Counter
{
public:
    void increment()
    {
        count += 1;
    }

    int value()
    {
        return count;
    }

private:
    int count = 0;
};

Multiple Independent Objects

Objects of the same class are independent: operating on one object's data members does not affect any other object.

c1 and c2 each have their own count. After the calls shown, c1.value() is 2 and c2.value() is 1 — incrementing c1 never touched c2's data.

Counter c1;
Counter c2;
c1.increment();
c1.increment();
c2.increment();
std::cout << c1.value() << ' ' << c2.value();

Arrays of Objects

An array can hold multiple objects of the same class type, laid out as consecutive elements, each with its own independent state.

rects holds three independent Rectangle objects. rects[i] accesses the i-th object, and rects[i].area() calls area on that particular object.

Rectangle rects[3] = { { 1.0, 2.0 }, { 3.0, 4.0 }, { 5.0, 6.0 } };
for (int i = 0; i < 3; ++i)
{
    std::cout << rects[i].area() << '\n';
}

Nested Objects

A class can have a data member whose type is itself another class. This is called composition: an object of one class contains, and manages, an object of another class as part of its own state.

Every Circle object contains a full Point object as its center member, not merely a reference to one. Constructing a Circle also constructs its center; the two objects' lifetimes are tied together.

c.center.x reaches through the nested object: c.center accesses the Point member, and .x then accesses that Point's own data member.

class Point
{
public:
    double x = 0.0;
    double y = 0.0;
};

class Circle
{
public:
    Point center;
    double radius = 0.0;
};
Circle c;
c.center.x = 5.0;
c.center.y = 5.0;
c.radius = 2.0;

Passing Objects to Functions

An object can be passed to a function by value, which copies it, or by reference, which lets the function operate on the caller's original object. Passing by const reference is common for larger objects because it avoids the cost of a copy while still preventing the function from modifying the caller's object; the mechanics of how the copy itself is made belong to the Copy Semantics topic.

totalArea takes r by const reference: it can read r but not modify it, and no copy of the Rectangle is made. scale takes r by (non-const) reference so that its modifications are visible in the caller's object.

double totalArea(const Rectangle& r)
{
    return r.area();
}

void scale(Rectangle& r, double factor)
{
    r.width *= factor;
    r.height *= factor;
}

Returning Objects from Functions

A function can return an object by value. The returned object is a separate value the caller receives, constructed from the object named in the return statement; how that transfer is carried out efficiently belongs to the Copy Semantics and Move Semantics topics.

makeUnitSquare creates a local Rectangle and returns it by value. square receives the resulting Rectangle object.

Rectangle makeUnitSquare()
{
    Rectangle r{ 1.0, 1.0 };
    return r;
}

Rectangle square = makeUnitSquare();

The this Pointer

Inside a non-static member function, the identifier this is a pointer to the object the function was called on. Referring to a data member by its plain name inside a member function is equivalent to going through this — the compiler does that implicitly.

this->count and count refer to the same data member here; writing this-> explicitly is optional in ordinary cases but makes clear that count belongs to the calling object. this is most useful when a parameter name would otherwise hide a data member's name, or when a member function needs to refer to, or return, the whole object it was called on.

class Counter
{
public:
    void increment()
    {
        this->count += 1;
    }

    int value() const
    {
        return this->count;
    }

private:
    int count = 0;
};

Basic Encapsulation

Encapsulation is the practice of bundling an object's data together with the functions that operate on it, and controlling access to that data through a defined interface rather than exposing it directly. Combining private data members with a set of public member functions is the most basic form of encapsulation in C++.

The only way to read or change a Temperature object's value from outside the class is through setCelsius, getCelsius, and getFahrenheit. This keeps the internal representation (storing Celsius) free to change later without breaking code that uses the class through its public interface.

class Temperature
{
public:
    void setCelsius(double c)
    {
        celsius = c;
    }

    double getCelsius() const
    {
        return celsius;
    }

    double getFahrenheit() const
    {
        return celsius * 9.0 / 5.0 + 32.0;
    }

private:
    double celsius = 0.0;
};

Best Practices

1. Keep data members private and expose behavior through a clear set of public member functions, rather than making every data member public by default.

2. Prefer struct for simple, passive data-holding types with no invariants to maintain, and class when access control and a controlled interface matter.

3. Pass larger objects to functions by const reference when the function only needs to read them, and by (non-const) reference when the function needs to modify the caller's object.

4. Give data members sensible default values so that an object is never left holding indeterminate state before it is explicitly set.

5. Use this-> only when it clarifies which entity a name refers to, such as when a parameter name matches a data member name; otherwise plain member names are enough.

Complete Example

Two independent BankAccount objects are stored in an array. Each has its own private balance, modified only through its public deposit and withdraw functions, and printed through a function that takes it by const reference.

accounts[0] is Alice with balance 100 - 20 = 80 accounts[1] is Bob with balance 50 printAccount reads each object's owner and balance through its public interface The program's output.

#include <iostream>
#include <string>

class BankAccount
{
public:
    std::string owner;

    void deposit(double amount)
    {
        balance += amount;
    }

    bool withdraw(double amount)
    {
        if (amount > balance)
        {
            return false;
        }
        balance -= amount;
        return true;
    }

    double getBalance() const
    {
        return balance;
    }

private:
    double balance = 0.0;
};

void printAccount(const BankAccount& account)
{
    std::cout << account.owner << ": " << account.getBalance() << '\n';
}

int main()
{
    BankAccount accounts[2];
    accounts[0].owner = "Alice";
    accounts[1].owner = "Bob";

    accounts[0].deposit(100.0);
    accounts[1].deposit(50.0);
    accounts[0].withdraw(20.0);

    for (int i = 0; i < 2; ++i)
    {
        printAccount(accounts[i]);
    }

    return 0;
}
Alice: 80
Bob: 50

Mental Model

Think of a class as a compile-time description and each object as a separate, runtime stamping-out of that description, with its own storage.

The class exists once; objects are created, and destroyed, independently of one another, each carrying its own copy of the data members.

class Rectangle (blueprint)         Objects (stamped instances)
  width  : double                    r1: [ width=1.0 | height=1.0 ]
  height : double                    r2: [ width=3.0 | height=4.0 ]
  area()                             r3: [ width=5.0 | height=6.0 ]

One class definition -> many independent objects, each with its own state,
all sharing the same member function code (area()).

Quick Reference

A quick-reference summary of the core class and object mechanics covered in this topic.

Concept               | Example                        | Meaning
Class definition      | class C { ... };               | Describes a new type
Data member            | double width;                  | Per-object state
Member function         | double area() { ... }          | Per-object behavior
Create object           | Rectangle r;                   | Instantiate the class
Access member            | r.width, r.area()             | Reach a member via .
Access specifier          | public: / private:            | Control member visibility
class vs struct           | class = private default        | struct = public default
Nested object              | Point center; (member)         | Composition
Pass by const reference     | void f(const Rectangle& r)     | Read without copying
this pointer                  | this->width                   | Points to the calling object

Key Takeaways

A class is a blueprint describing data members and member functions; an object is a distinct instance of that class with its own storage.

Every object has its own identity and its own state, even when several objects of the same class hold identical values.

class and struct differ only in default member access — private for class, public for struct.

Access specifiers (public, private, protected) control which code can reach a member directly, forming the basis of encapsulation.

Composition lets one class contain another as a full member object, not just a reference to one.

Passing and returning objects by value copies them; passing by const reference avoids that cost when a copy isn't needed.

Inside a member function, this points to the object the function was called on.

What is the difference between a class and an object?

A class is a user-defined type — a blueprint specifying what data members and member functions objects of that type will have. It occupies no storage for any particular instance's values. An object is a concrete instance of a class: creating one allocates storage for its own copy of the class's data members, which can then be read and modified independently of any other object of the same class.

Given struct Point { double x; double y; }; and class PointC { double x; double y; };, why can code outside write Point p; p.x = 1; but not PointC pc; pc.x = 1;?

struct and class differ only in default member access. struct members default to public, so p.x is accessible from outside the class. class members default to private, so pc.x is not accessible outside PointC's own member functions — writing pc.x = 1; from outside fails to compile unless x is explicitly marked public.

Two Rectangle objects a and b hold identical width and height values. Are a and b the same object? How would you demonstrate the answer in code?

No — they are two distinct objects with identical state, not the same object. Object identity is about storage location, not stored values. This can be demonstrated by comparing their addresses: &a == &b evaluates to false even though a and b hold the same width and height.

Why does a Circle class holding a Point center member (composition) tie the lifetimes of the Circle and its center together?

Because center is a full Point object stored directly inside Circle, not a pointer or reference to a Point that lives elsewhere. Constructing a Circle object constructs its center member as part of the same operation, and destroying the Circle destroys center along with it — the nested object's lifetime cannot outlive, or be separated from, the containing object's lifetime.

Inside a member function, what does the this pointer refer to, and when is writing this-> necessary rather than optional?

this is a pointer to the specific object the member function was called on. Writing this-> to access a member is usually optional, since a plain member name inside a member function already implicitly refers to this object's member. It becomes useful, and sometimes necessary for clarity, when a function parameter or local variable has the same name as a data member, so that this->name unambiguously refers to the data member rather than the parameter.

Trace through this code. What does it print, and why does calling a.increment() three times not affect b.value()?

a and b are independent Counter objects, each with its own count data member. Calling a.increment() three times only modifies a's own count, taking it from 0 to 3; it has no effect on b's separate count, which increment() only raised to 1 through its own call. Output: 3 1.

Write a class named Stack3 that models a fixed-capacity stack of 3 integers using a plain array data member and an int size member (starting at 0). Give it public member functions push(int value) that adds a value if there's room, and top() that returns the most recently pushed value. Demonstrate pushing 10, 20, and 30, then printing top().

Each push writes the new value at index size and then increments size, so after three pushes data holds {10, 20, 30} and size is 3. top() reads data[size - 1], which is data[2], the most recently pushed value. The program prints 30.

Trace through this code. What does it print, and why does the call to tryModify not change a.x while the call to modify does?

tryModify takes its parameter by value, so p inside that function is a separate copy of a; modifying p.x only changes the copy, leaving the caller's a untouched. modify takes its parameter by reference, so p inside that function refers to the same object as a; modifying p.x there changes a directly. Output: 1 100.

Write a class named Pair that has two public double data members, first and second, and a public member function swapValues() that exchanges first and second using the this pointer explicitly (this->first, this->second). Create a Pair with first=1.0 and second=2.0, call swapValues(), and print both members.

swapValues stores this->first (1.0) in temp, then assigns this->second's value (2.0) into this->first, and finally assigns temp (the original 1.0) into this->second. this->first and first refer to the same data member; the explicit this-> is used here for clarity but is not required. After the call, p.first is 2.0 and p.second is 1.0, so the program prints 2 1.

class Counter
{
public:
    void increment() { count += 1; }
    int value() { return count; }
private:
    int count = 0;
};

Counter a;
Counter b;
a.increment();
a.increment();
a.increment();
b.increment();
std::cout << a.value() << ' ' << b.value();
class Point { public: double x; double y; };

void tryModify(Point p) { p.x = 100.0; }
void modify(Point& p) { p.x = 100.0; }

Point a{ 1.0, 2.0 };
tryModify(a);
std::cout << a.x << ' ';
modify(a);
std::cout << a.x;