Object Oriented Programming using 'C++

Chapter 1
C++ and the Object-Oriented Paradigm

Post Graduate Program in Computer Science Engineering
Object Oriented Programming using 'C++   •   Post Graduate

1. C++ and the Object-Oriented Paradigm

Introduction and Context

C++ occupies a distinctive position in computing because it combines close control over machine resources with powerful abstractions for large-scale software design. It originated as an extension of the C language, but its development introduced classes, inheritance, virtual functions, templates, exceptions, and other mechanisms that support several programming paradigms. Modern C++ is therefore not merely an object-oriented language. It is a multiparadigm language in which object-oriented, procedural, generic, and functional techniques can be combined according to the problem being solved.

The significance of C++ is best understood through the relationship between programming models and software complexity. A small procedural program may be organized as a sequence of functions operating on data. As the program grows, however, data representation, access rules, dependencies, and changes in requirements become difficult to manage. Object-oriented programming addresses these difficulties by treating a software system as a set of interacting objects that combine state and behaviour. The approach does not eliminate complexity; it provides ways to localize, control, and reason about it.

C++ is widely used in systems software, game engines, embedded systems, financial platforms, scientific computing, browsers, compilers, and performance-critical infrastructure. Its importance at postgraduate level lies not only in learning syntax, but in understanding design decisions: when to hide representation, when to use inheritance, when composition is preferable, how generic abstractions affect performance, and how source code becomes an executable program.

Learning Objectives

After studying this chapter, a student should be able to:

  • explain abstraction, encapsulation, identity, state, behaviour, inheritance, and polymorphism;
  • distinguish procedural, object-oriented, generic, and functional programming paradigms;
  • model a problem using classes and object relationships;
  • explain the roles of the preprocessor, compiler, linker, build system, and debugger;
  • identify design trade-offs involving inheritance, composition, ownership, and performance;
  • apply object-oriented principles to a realistic software-development scenario;
  • recognize professional, ethical, and maintenance risks in C++ development.

From Procedures to Objects

A programming paradigm is a general way of organizing computation. It influences what a programmer regards as a primary unit of design, how data is represented, and how operations are expressed. Paradigms are not mutually exclusive languages or rigid classifications. A C++ program may use a procedural algorithm, generic containers, functional-style transformations, and object-oriented interfaces in the same codebase.

The procedural view

In procedural programming, a problem is decomposed into procedures or functions. Data is passed to functions, modified, and returned. This model is natural for algorithms whose major concern is a clear sequence of operations. For example, a sorting routine can be written as a function that receives a range and rearranges its elements.

The strength of the procedural model is directness. Control flow is visible, and small algorithms are often easy to test. Its difficulty appears when many functions share mutable data. A change to the data representation may require changes in numerous functions. Access discipline may depend on programmer convention rather than language-enforced interfaces.

The object-oriented view

Object-oriented programming organizes software around objects. An object is an identifiable entity that possesses state and exposes behaviour. A `BankAccount` object, for example, may have a balance and account identifier as state, while `deposit()` and `withdraw()` represent behaviour. The object controls the rules governing changes to its state.

A class is a specification or blueprint from which objects can be created. It defines data members, member functions, access control, and invariants. An invariant is a condition that should remain true for every valid object, such as the requirement that an account balance cannot become negative when overdrafts are forbidden.

Object orientation is not simply the practice of placing functions inside a class. Its deeper purpose is to establish meaningful boundaries around responsibility. A well-designed class hides representation, offers operations that express its purpose, and prevents clients from creating invalid states.

Generic programming

Generic programming focuses on algorithms and data structures that are independent of particular types. C++ templates support this approach. A function template can sort integers, strings, or user-defined objects provided that the required operations are available. The central idea is often described as programming against capabilities rather than concrete class hierarchies.

Generic programming promotes reuse without requiring a common inheritance relationship. The standard library illustrates this through containers, iterators, algorithms, ranges, and concepts. Its major design question is not “What class is this object derived from?” but “What operations does this type support?”

Functional programming

Functional programming treats computation as the evaluation and composition of functions. It emphasizes expressions, immutability, referential transparency, and minimizing side effects. C++ is not a purely functional language, but it supports functional techniques through lambdas, higher-order algorithms, `const`, immutable values, and standard algorithms such as `transform`, `accumulate`, and `filter`-like range operations.

Functional techniques can make data processing easier to reason about, especially when operations are independent and state changes are undesirable. However, resource management, device interaction, and performance-sensitive systems often require controlled mutation. C++ permits both styles, making design discipline essential.

The Conceptual Vocabulary of Objects

Identity

Identity distinguishes one object from another even when two objects have equal values. Two `Point` objects may both contain coordinates `(2, 3)`, yet they can be separate entities with different lifetimes and addresses. Identity matters in systems that model users, transactions, files, network connections, or physical devices.

State

State is the collection of values associated with an object at a particular time. A file object may contain an open or closed status, a current position, and access permissions. State changes through defined operations. Uncontrolled state changes are a frequent source of bugs, especially in concurrent or resource-owning objects.

Behaviour

Behaviour describes what an object can do and how it responds to messages or method calls. Behaviour includes successful operations, error handling, performance expectations, and effects on related objects. A class interface should communicate behaviour at a level meaningful to its clients rather than expose unnecessary implementation details.

Abstraction

Abstraction is the process of identifying essential characteristics while suppressing irrelevant detail. A queue can be understood through operations such as `push`, `pop`, and `front`, without requiring users to know whether it is implemented with an array or linked list. Abstraction reduces cognitive load and permits implementation changes behind a stable interface.

Abstraction has several dimensions. Data abstraction hides representation; procedural abstraction hides algorithmic steps behind functions; and interface abstraction exposes contractual capabilities without committing clients to implementation details. Good abstraction is selective: hiding everything may make a component unusable, while exposing everything creates coupling.

Encapsulation

Encapsulation combines data and the operations that govern it, while controlling access to internal representation. In C++, `private`, `protected`, and `public` access specifiers provide mechanisms for expressing this control. Encapsulation is not identical to information hiding. A class may encapsulate members structurally, whereas information hiding concerns the design decision to conceal details likely to change.

For example:

```cpp

class Temperature {

double celsius{};

public:

explicit Temperature(double value) : celsius(value) {}

double value() const { return celsius; }

void increase(double amount) { celsius += amount; }

};

```

The class controls access to `celsius` and can later change its representation or validation rules without requiring clients to manipulate the member directly. A production version might reject invalid values or use a stronger unit type.

Inheritance

Inheritance expresses a relationship in which a derived class extends or specializes a base class. It may support reuse, but its most important object-oriented role is substitutability. If `Square` is publicly derived from `Shape`, code using a `Shape` should be able to use a `Square` without violating expectations.

Public inheritance should represent an “is-a” relationship supported by the base class contract. It should not be used merely because one class happens to reuse implementation from another. Private inheritance and composition may be more appropriate when reuse is the main goal.

Inheritance can be single, multiple, multilevel, or hierarchical. Multiple inheritance is powerful but introduces issues such as ambiguity and the diamond problem. Virtual inheritance can address shared base subobjects in particular designs, but complexity should be justified by a clear model.

Polymorphism

Polymorphism means that one interface can represent different underlying forms. C++ supports several kinds:

  • Ad hoc polymorphism: function or operator overloading, where the same notation has different implementations for different parameter types.
  • Parametric polymorphism: templates, where algorithms are written independently of a specific type.
  • Subtype polymorphism: inheritance and virtual functions, where a base-class interface can refer to derived objects.
  • Coercion polymorphism: conversions allow one type to be used where another compatible type is expected.

Runtime polymorphism is commonly implemented through virtual functions. If a base class is intended for polymorphic use, its destructor should generally be virtual so that deleting a derived object through a base pointer is safe. The `override` specifier helps the compiler verify that a derived function actually overrides a virtual base function.

```cpp

#include <memory>

#include <vector>

struct Shape {

virtual double area() const = 0;

virtual ~Shape() = default;

};

struct Circle : Shape {

double radius;

explicit Circle(double r) : radius(r) {}

double area() const override { return 3.14159 radius radius; }

};

```

The client can process a collection of `Shape` objects without knowing each concrete type. The benefit is extensibility; the cost may include indirection, more complex ownership, and difficulty tracing control flow.

Composition and delegation

Composition means that an object contains or uses other objects. A `Car` may contain an engine object; a `ReportGenerator` may use a formatter and a data source. Composition often produces lower coupling than inheritance because the assembled parts can be replaced independently. Delegation occurs when one object forwards a responsibility to another.

A useful design heuristic is “prefer composition over inheritance” when the relationship is not a genuine subtype relationship or when implementation reuse is the primary motivation. This is not an absolute rule. Stable abstractions, frameworks, and domain hierarchies may make inheritance appropriate.

C++ as a Translation and Execution System

A C++ program passes through several technical stages. Understanding these stages improves debugging and build management.

The preprocessor handles directives such as `#include` and conditional compilation. It produces a transformed source stream, but it does not understand C++ classes or types. The compiler translates source code into an intermediate representation and typically generates an object file. During compilation, syntax, type correctness, overload resolution, templates, and many semantic rules are checked.

The linker combines object files and libraries. It resolves references such as a function called in one source file but defined in another. Link errors are therefore different from compile errors: the source may be syntactically valid, yet a required definition may be missing or incompatible.

A build system records dependencies and commands required to produce targets. Tools such as Make, Ninja, and CMake can automate compilation, linking, testing, installation, and configuration. CMake is commonly used to generate build files for different platforms, while the actual compilation is performed by a compiler such as GCC, Clang, or Microsoft Visual C++.

A typical workflow is:

1. edit source and header files;

2. configure the project and select a toolchain;

3. compile changed translation units;

4. link object files and libraries;

5. run tests or the program;

6. inspect failures using diagnostics and a debugger;

7. repeat with controlled configuration changes.

A debugger allows execution to be paused at breakpoints, inspected through stack frames, and examined using variables and expressions. Watchpoints can detect when a value changes. Sanitizers are complementary tools: address sanitizers detect many memory errors, undefined-behaviour sanitizers identify problematic operations, and thread sanitizers help detect certain data races. Compiler warnings, static analysis, unit tests, and code review should be used together rather than treating the debugger as the only source of evidence.

A Framework for Object-Oriented Design

A practical design process begins by identifying responsibilities rather than immediately writing classes. Domain nouns may suggest candidate objects, but nouns alone do not establish good abstractions. For each candidate, ask what information it owns, what operations it guarantees, what invariant it protects, and which other objects it must know about.

A class should have a cohesive responsibility. High cohesion means that its members support a closely related purpose. Low coupling means that changes in one class require few changes elsewhere. Interfaces should be narrow enough to prevent clients from depending on irrelevant details. Dependency inversion encourages high-level policy to depend on abstractions rather than concrete implementation classes.

Contracts are another useful framework. A precondition states what must be true before an operation; a postcondition states what it guarantees afterward; and an invariant remains true throughout the valid lifetime of an object. Contracts may be documented, asserted, tested, or supported by language features and static analysis.

Resource management is central to C++. Resources include memory, files, locks, sockets, and transactions. The RAII principle—resource acquisition is initialization—binds resource lifetime to object lifetime. A constructor acquires or establishes a resource, and a destructor releases it. Standard library types such as `std::vector`, `std::string`, `std::unique_ptr`, and lock guards embody this principle. Prefer automatic resource management to manual `new` and `delete` wherever possible.

Ownership must be explicit. `std::unique_ptr` represents exclusive ownership, `std::shared_ptr` represents shared ownership with reference counting, and a raw pointer often represents a non-owning observation when documented accordingly. Misunderstood ownership leads to leaks, double deletion, dangling references, and cyclic shared ownership.

Applied Scenario: Refactoring a Logistics Platform

Background

Consider an illustrative logistics company whose original delivery application was written as a collection of procedural functions. Shipment records were stored in public structures, and separate functions calculated charges, selected carriers, printed labels, and updated delivery status.

Situation and problem

As the company added express, refrigerated, and international shipments, the code accumulated conditional statements. A change to pricing required modifications in several modules. Invalid status transitions were possible, and tests had to construct large data structures with fields unrelated to the behaviour being tested.

Analysis

The main difficulty was not the absence of classes but the absence of boundaries. Shipment state could be modified without validation; pricing policy was mixed with storage and presentation; and carrier selection depended directly on concrete carrier implementations. The team identified shipment, pricing policy, carrier, and label formatter as separate responsibilities.

Approach

A `Shipment` class was introduced with private state and operations such as `dispatch()` and `markDelivered()`. It rejected invalid transitions. Pricing was represented by a policy interface, with concrete standard and express policies. Carrier selection depended on an abstract `Carrier` capability rather than a specific company class. Composition allowed a shipment service to receive a pricing policy and carrier through dependency injection.

Generic library components were used for collections and algorithms, while lambdas handled small filtering operations. Ownership was expressed with `unique_ptr` for services that owned their policies. Unit tests verified invariants and policy calculations; integration tests verified interactions with external carrier systems.

Outcome

The illustrative redesign localized changes. Adding a new pricing policy did not require changing `Shipment`, and the user-interface layer no longer manipulated shipment representation. The system still required careful integration testing and did not become automatically simple: additional abstractions introduced interfaces and configuration decisions.

Learning points

The case demonstrates that object orientation is valuable when it protects invariants, separates responsibilities, and supports controlled variation. It also shows that inheritance is only one tool. Composition, generic algorithms, interfaces, and disciplined ownership contributed more to maintainability than a deep class hierarchy would have done.

Common Errors, Risks, and Limitations

A frequent mistake is creating classes that merely expose public data and provide trivial getters and setters. Such classes may add ceremony without encapsulating meaningful rules. The opposite mistake is designing “god objects” that know too much and perform unrelated operations.

Deep inheritance hierarchies can make behaviour difficult to predict. Fragile base-class problems arise when changes to a base class unexpectedly affect derived classes. Inappropriate polymorphism may also introduce runtime costs or obscure ownership. Templates improve reuse but can produce complex diagnostics and increase compilation time. Excessive abstraction may make a small program harder to understand.

C++ permits undefined behaviour, including out-of-bounds access, use-after-free, invalid casts, data races, and signed overflow in relevant contexts. Undefined behaviour is not a predictable error condition; the optimizer may assume it never occurs. Resource leaks and race conditions can create security vulnerabilities, especially in network-facing or privileged software.

Professional developers should avoid suppressing warnings without justification, should treat external input as untrusted, and should use safe interfaces and tested libraries. Code should be reviewed for lifetime, exception safety, concurrency, and failure handling. A public API should document ownership, thread-safety guarantees, error behaviour, and compatibility expectations.

Ethically, software engineers are responsible for reducing foreseeable harm. In safety-critical, financial, medical, or privacy-sensitive systems, design decisions must be traceable, tested, and reviewed. Open-source licenses must be respected, dependencies should be monitored for vulnerabilities, and build pipelines should protect artifacts and credentials. Automated code-generation tools may assist development, but their output requires human verification for correctness, licensing, security, and maintainability.

Contemporary Directions

Modern C++ development increasingly emphasizes value semantics, move operations, ranges, concepts, modules, concurrency facilities, and compile-time computation. Concepts allow generic interfaces to express required capabilities more clearly than unconstrained templates. Modules seek to reduce problems associated with textual inclusion and improve build isolation, although adoption depends on toolchain and ecosystem support.

Parallel and heterogeneous computing have also influenced C++ practice. Standard concurrency facilities support threads, synchronization, futures, and atomic operations, while specialized libraries target GPUs and distributed systems. These capabilities increase the need for precise ownership and synchronization models. Artificial intelligence tools can assist with code completion, refactoring, and test generation, but they do not replace compiler diagnostics, security analysis, profiling, or domain review.

Best Practices for C++ and Object-Oriented Design

  • Prefer clear interfaces and private representation.
  • Give each class a focused responsibility and protect its invariants.
  • Prefer composition when reuse does not imply substitutability.
  • Use virtual functions only where runtime substitutability is required.
  • Express ownership through RAII and appropriate smart pointers.
  • Prefer standard-library types and algorithms over unnecessary custom machinery.
  • Compile with strong warnings and use sanitizers and static analysis.
  • Keep builds reproducible through explicit configurations and dependency management.
  • Test behaviour, boundaries, failure paths, and resource lifetimes.
  • Document assumptions about ownership, exceptions, concurrency, and performance.

Chapter Summary

C++ supports procedural, object-oriented, generic, and functional programming. Object-oriented design models systems through objects with identity, state, and behaviour. Abstraction selects essential concepts; encapsulation protects representation and invariants; inheritance expresses specialization; polymorphism allows one interface to support multiple implementations; and composition assembles collaborating objects. Good design emphasizes cohesion, low coupling, explicit contracts, and appropriate ownership.

C++ programs are processed through preprocessing, compilation, and linking, then managed through build systems and examined with debuggers, sanitizers, tests, and static-analysis tools. The language offers substantial power but also exposes risks involving undefined behaviour, lifetime, concurrency, complexity, and security. Professional C++ development therefore requires both knowledge of language mechanisms and disciplined engineering judgment.

Key Terms

Abstraction: Representation of essential features while hiding unnecessary detail.

Behaviour: Operations and responses an object provides.

Build system: Tool or framework that manages compilation, linking, testing, and related tasks.

Class: A user-defined type specifying data, operations, and access rules.

Composition: Constructing an object from collaborating or contained objects.

Encapsulation: Combining state and operations while controlling access to representation.

Identity: Property that distinguishes one object from another.

Inheritance: Mechanism through which a derived class extends or specializes a base class.

Invariant: Condition that remains true for every valid object state.

Linker: Tool that combines object files and resolves external references.

Object: An identifiable entity with state and behaviour.

Paradigm: A general model for organizing computation and software design.

Polymorphism: Ability of one interface or notation to represent multiple forms.

Precondition: Requirement that must hold before an operation begins.

RAII: C++ resource-management principle tying resource lifetime to object lifetime.

State: Values associated with an object at a given time.

Subtype polymorphism: Runtime substitutability through inheritance and virtual functions.

Template: C++ mechanism for expressing type-independent code.

Undefined behaviour: Program execution for which the C++ standard imposes no requirements.

Virtual function: Member function whose implementation can be selected dynamically through a base interface.

Review Questions

Short Answer Questions

1. What distinguishes object identity from object state?

2. Explain the difference between abstraction and encapsulation.

3. Why should a polymorphic base class commonly have a virtual destructor?

4. What is the role of a linker in building a C++ program?

5. Define RAII and name two resources to which it can apply.

Long Answer Questions

1. Compare procedural, object-oriented, generic, and functional programming in C++, including an appropriate use for each.

2. Explain inheritance, composition, and delegation, and discuss why composition is often preferred.

3. Describe the complete path from C++ source code to an executable program.

4. Discuss how contracts, invariants, cohesion, and coupling contribute to object-oriented design.

5. Explain major C++ risks involving ownership, undefined behaviour, templates, and concurrency, and propose controls for them.

Analytical / Application Questions

1. A payment system contains a large `Payment` base class with many subclasses and repeated conditional logic. Analyse the likely design problems and propose a composition- and policy-based alternative.

2. A program compiles successfully but fails during linking with an unresolved reference. Explain what this indicates and describe a systematic diagnostic process.

3. Design the main abstractions for a library lending system. Identify object identities, state, behaviours, invariants, ownership relationships, and one place where runtime polymorphism or generic programming would be appropriate.

22. Multiple Choice Questions with Answers

Question 1

Which design decision most directly demonstrates encapsulation?

A. Making all data members public for convenient access

B. Allowing a class to control state changes through public operations

C. Replacing every class with a free function

D. Using inheritance only to reuse implementation

Correct Answer: B. Allowing a class to control state changes through public operations

Question 2

Two objects contain identical coordinate values but occupy different storage locations. Which concept distinguishes them?

A. Identity

B. Abstraction

C. Compilation

D. Delegation

Correct Answer: A. Identity

Question 3

Which situation best supports the use of subtype polymorphism?

A. An algorithm works only with integers

B. A client processes different shapes through a common Shape interface

C. A function has two parameter lists

D. A resource is released by a destructor

Correct Answer: B. A client processes different shapes through a common Shape interface

Question 4

What is the primary role of a linker?

A. Checking indentation rules

B. Expanding preprocessor macros only

C. Combining object files and resolving external references

D. Monitoring variables at a breakpoint

Correct Answer: C. Combining object files and resolving external references

Question 5

A class owns a file handle and releases it automatically when the object leaves scope. Which principle is illustrated?

A. RAII

B. Multiple inheritance

C. Ad hoc polymorphism

D. Preprocessing

Correct Answer: A. RAII

Question 6

Why might composition be preferable to inheritance for a service that uses a pricing strategy?

A. Composition prevents all runtime errors

B. Composition allows the strategy to be replaced without implying a subtype relationship

C. Inheritance cannot contain member functions

D. Composition always has zero runtime cost

Correct Answer: B. Composition allows the strategy to be replaced without implying a subtype relationship

Question 7

Which statement about generic programming is most accurate?

A. It requires every type to inherit from one base class

B. It writes algorithms in terms of capabilities supported by types

C. It is limited to graphical applications

D. It eliminates the need for testing

Correct Answer: B. It writes algorithms in terms of capabilities supported by types

Question 8

A program compiles but reports an unresolved function definition during linking. What is the most likely category of problem?

A. A missing or incompatible definition across translation units

B. A violated object invariant detected at runtime

C. A debugger breakpoint failure

D. A preprocessing comment error

Correct Answer: A. A missing or incompatible definition across translation units

Question 9

Which practice most directly reduces lifetime and ownership errors in modern C++?

A. Using raw owning pointers everywhere

B. Managing resources through RAII and appropriate smart pointers

C. Disabling compiler warnings

D. Making every member variable global

Correct Answer: B. Managing resources through RAII and appropriate smart pointers

Question 10

In the logistics scenario, why was a pricing policy interface useful?

A. It forced all shipments to use identical pricing

B. It separated pricing variation from shipment state and allowed policies to be replaced

C. It removed the need for any tests

D. It made inheritance mandatory for every class

Correct Answer: B. It separated pricing variation from shipment state and allowed policies to be replaced

Object Oriented Programming using 'C++
Chapter 1 of 15