08 Classes and Objects

A progressive guide to defining C++ classes, creating objects, controlling access, initializing state, protecting invariants, and managing object lifetimes safely.

From Classes to Objects

A is a user-defined type, while an is an instance of that type. The describes the available data and operations; each stores its own state in storage.

A definition contains a name and a body of member declarations. The semicolon after the closing brace is required. For example, a Rectangle can declare public double members named width and height. After the definition, Rectangle first; and Rectangle second; create two objects of the same type. Assigning first.width = 4.0 does not change second.width because the objects have separate non-static state.

A can be defined inside the or outside it. An outside definition identifies the with the scope-resolution operator ::; a declaration such as double area() const; can be defined as double Rectangle::area() const. A defined inside a definition is implicitly inline when appropriate.

Takeaway: A defines a type, and an is a concrete instance with its own state and lifetime.

State and Behavior

represent an 's state. They may have fundamental types, library types, arrays, pointers, or other types. Every normally has its own non-static , so an Account can have its own owner, balance, and number independently of other accounts.

A default member initializer supplies a starting value when a does not provide another one. For example, declaring int value = 0; inside a Counter makes the default state explicit and helps prevent an uninitialized value.

A defines behavior associated with a . It operates on a particular when called with the member-access operator, as in account.deposit(50.0). Inside a non-static , the implicit this pointer refers to the on which the function was called, so balance and this->balance refer to the same member in that context.

A promises not to modify the observable state of the through which it is called. Marking a read-only operation with const allows it to be called on both ordinary and const objects, while a non-const operation generally cannot be called on a const .

Takeaway: store state, member functions provide behavior, and const communicates that an operation is read-only.

and Interfaces

determines which code may use each member. public members are accessible from code that can name the . private members are accessible only to members and friends of the . protected members are accessible to the , its friends, and derived classes.

Access specifiers apply to the declarations that follow them until another access specifier appears. Members of a are private by default, whereas members of a struct are public by default. A commonly places its public interface first and its implementation details under private.

For example, a Person can expose set_age(int) and get_age() const as public functions while keeping int age = 0 private. The setter can accept a new value only when it satisfies the 's rule, such as being nonnegative. Outside code can call person.set_age(25) and person.get_age(), but it cannot directly assign person.age.

This separation makes the public operations the interface and keeps representation details hidden. It prevents outside code from bypassing rules that protect a valid state.

Takeaway: Use public members for intentional operations and private members to protect representation and validity rules.

Constructors and Initialization

A initializes an automatically when it is created. Its name matches the name, and it has no return type. Constructors may be overloaded so that objects can be created with different sets of initial values.

For example, a Point can provide Point() to create the origin and Point(int initial_x, int initial_y) to initialize a chosen location. Point origin; calls the no-argument , while Point location(3, 4); calls the two-argument .

The is the portion after the declaration that initializes members before the body runs. It is the preferred mechanism for initializing members, especially const members, reference members, and members whose types do not have a default .

Members are initialized in the order in which they are declared in the , regardless of the order written in the initializer list. Declare members in a logical dependency order and write the initializer list in that same order.

A with no required arguments is a default . A can also be marked explicit to prevent unintended implicit conversions. With explicit Meters(double value), Meters distance(5.0); is valid, while Meters distance = 5.0; is rejected.

Takeaway: Constructors establish initial state, and member initializer lists are the preferred way to initialize members.

in Practice

combines state and behavior while controlling how state is accessed. A common design keeps private and exposes public member functions that enforce the 's rules.

A BankAccount can store a private double balance and provide deposit, withdraw, and get_balance operations. The can replace a negative initial balance with 0.0. A withdrawal can return false when its amount is negative or greater than the current balance; otherwise, it subtracts the amount and returns true. A deposit can accept only an amount that satisfies its validation rule.

Outside code therefore depends on operations rather than on the representation of the balance. It cannot directly assign an invalid value, and the implementation can change later without requiring changes to code that uses the public interface.

protects an invariant: a condition that should remain true for a valid . Validation at the boundary ensures that public operations preserve that condition.

Takeaway: protects invariants and reduces coupling by giving users a deliberate, stable interface.

and Resource Management

begins after storage has been obtained and initialization is complete. For a , construction establishes the initial state. The lifetime ends when destruction begins or when the 's storage is released or reused.

An automatic declared inside a block is constructed when execution reaches its declaration and is usually destroyed when execution leaves that block. For example, a local BankAccount account(100.0); can receive a deposit and then be destroyed automatically when process ends.

An created with new has dynamic storage duration and remains alive until it is destroyed with delete. In modern C++, owning raw pointers should generally be replaced with resource-managing types such as std::unique_ptr and std::make_unique. An held by std::unique_ptr is destroyed automatically when the owning pointer leaves its scope.

An declared at namespace scope or with static can have static storage duration. Such an is initialized before or during program startup and destroyed during program termination, subject to the language's initialization rules.

A is written with the name preceded by ~, such as ~Logger(). It can release resources owned by an . Many classes need no user-written because members such as std::string, std::vector, and smart pointers clean themselves up automatically. This automatic resource management is the basis of .

Using an after its lifetime has ended, such as dereferencing a pointer to a destroyed , produces undefined behavior.

Takeaway: Prefer ownership types that make destruction automatic, and never use an after its lifetime has ended.

Putting the Design Together

A complete design connects private state, construction, controlled operations, and automatic cleanup. Consider a Student with private std::string name, int id, and double grade .

Its can initialize name and id through a . A set_grade can accept a new value only when it is between 0.0 and 100.0, preserving the 's validity rule. A print can report the state without modifying the .

A typical use creates `Student student(