Encapsulation in C++ (and How to Do It in C): Meaning, Example and Benefits

Encapsulation means bundling data and the functions that work on that data into one unit, and restricting direct access to the data from outside that unit. In C++ the unit is a class: data members are made private, and outside code can only read or change them through public member functions that check every change. C has no classes, but you can still get the same effect with an opaque struct declared in a header and defined in a separate .c file. Both are shown below with complete, compiled examples.

Advertisement

Encapsulation in C++ illustrated as code on a screen

What is encapsulation? The meaning in plain words

Think of a bank account. You cannot walk into the bank’s database and type a new balance. You go through a counter or an app that checks your request: is the amount positive, is there enough money? The balance is hidden, and the only way to change it is through controlled operations. That is encapsulation.

In code, encapsulation gives you two things:

  • Bundling: the data (balance) and the operations on it (deposit, withdraw) live together.
  • Access control: other code cannot touch the data directly, so it can never be put into an invalid state, such as a negative balance.

Encapsulation is one of the four pillars of object-oriented programming, along with abstraction, inheritance and polymorphism.

Advertisement

Encapsulation in C++

C++ supports encapsulation directly through classes and three access specifiers:

SpecifierAccessible fromTypical use
privateOnly member functions (and friends) of the same classData members, internal helper functions
protectedThe class and classes derived from itMembers a subclass needs but outside code should not see
publicAnywhereThe interface: constructors, getters, setters, operations

Members of a class are private by default; members of a struct are public by default. That default is the only difference between the two keywords.

Getters and setters

A getter returns the value of a private member, usually marked const because it does not change the object. A setter changes it, but only after checking that the new value is valid. A good class does not give every member a plain setter; it offers meaningful operations instead, such as deposit() rather than setBalance().

Encapsulation in C++ example: a bank account

#include <iostream>
#include <string>

class BankAccount {
private:
    std::string owner;
    long long balance;   // in rupees; hidden from outside code

public:
    BankAccount(const std::string& name, long long opening)
        : owner(name), balance(0) {
        if (opening > 0) balance = opening;
    }

    bool deposit(long long amount) {
        if (amount <= 0) return false;       // reject zero or negative
        balance += amount;
        return true;
    }

    bool withdraw(long long amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }

    long long getBalance() const { return balance; }
    std::string getOwner() const { return owner; }
};

int main() {
    BankAccount acc("Asha", 5000);
    acc.deposit(1500);                        // balance 6500
    if (!acc.deposit(-200))
        std::cout << "Deposit refused: amount must be positive\n";
    if (!acc.withdraw(10000))
        std::cout << "Withdrawal refused: insufficient balance\n";
    acc.withdraw(2000);                       // balance 4500
    std::cout << acc.getOwner() << "'s balance: Rs " << acc.getBalance() << "\n";
    // acc.balance = 1000000;   // compile error: 'balance' is private
    return 0;
}

Output:

Deposit refused: amount must be positive
Withdrawal refused: insufficient balance
Asha's balance: Rs 4500

How it works:

Advertisement
  • The account opens with Rs 5000. The deposit of 1500 is accepted, giving 6500.
  • The deposit of -200 fails the amount <= 0 check, so the balance does not change.
  • The withdrawal of 10000 fails because it is more than the balance of 6500.
  • The withdrawal of 2000 succeeds, leaving 6500 – 2000 = 4500.
  • If you remove the // from the line acc.balance = 1000000;, the program no longer compiles. The compiler reports that balance is a private member of BankAccount. That compile-time error is encapsulation working.

Because every change passes through deposit() and withdraw(), the rule “balance can never go negative” is enforced in exactly one place. If the bank later adds a daily withdrawal limit, only withdraw() changes; code that uses the class does not.

Can you do encapsulation in C?

Yes, although the language gives you no private keyword. C, a procedural language (see what C programming is), has no classes, and every member of a struct is visible to any code that can see the struct’s definition. The standard technique is the opaque pointer:

  1. The header file declares the struct but does not define it (an incomplete type), plus the functions that work on it.
  2. Only the .c file defines the struct’s fields and the functions.
  3. Helper functions inside the .c file are marked static, which makes them invisible outside that file.

account.h

/* account.h - what other files are allowed to see */
#ifndef ACCOUNT_H
#define ACCOUNT_H

typedef struct Account Account;   /* incomplete type: fields hidden */

Account  *account_create(long long opening);
int       account_deposit(Account *a, long long amount);
int       account_withdraw(Account *a, long long amount);
long long account_balance(const Account *a);
void      account_destroy(Account *a);

#endif

account.c

/* account.c - the only file that knows the struct layout */
#include <stdlib.h>
#include "account.h"

struct Account {
    long long balance;
};

static int is_valid(long long amount) {   /* private to this file */
    return amount > 0;
}

Account *account_create(long long opening) {
    Account *a = malloc(sizeof *a);
    if (a) a->balance = is_valid(opening) ? opening : 0;
    return a;
}

int account_deposit(Account *a, long long amount) {
    if (!is_valid(amount)) return 0;
    a->balance += amount;
    return 1;
}

int account_withdraw(Account *a, long long amount) {
    if (!is_valid(amount) || amount > a->balance) return 0;
    a->balance -= amount;
    return 1;
}

long long account_balance(const Account *a) { return a->balance; }

void account_destroy(Account *a) { free(a); }

main.c

/* main.c - a user of the module */
#include <stdio.h>
#include "account.h"

int main(void) {
    Account *acc = account_create(5000);
    if (!acc) return 1;
    account_deposit(acc, 1500);
    if (!account_withdraw(acc, 10000))
        printf("Withdrawal refused\n");
    printf("Balance: %lld\n", account_balance(acc));
    /* acc->balance = 0;  error: incomplete type */
    account_destroy(acc);
    return 0;
}

Output:

Withdrawal refused
Balance: 6500

Here main.c only holds a pointer to an Account. It cannot read or write balance, because it never sees the struct’s fields: writing acc->balance = 0; gives a compile error about an incomplete type. The C standard library’s own FILE type is used in much the same way, since you work with FILE * through fopen() and fprintf() and are not meant to touch its fields.

The limits of the C approach: objects must be created on the heap through a create function, there is no automatic destructor (you must call account_destroy()), and there is no inheritance or protected access.

Advertisement

Encapsulation vs abstraction vs data hiding

PointEncapsulationAbstractionData hiding
MeaningBundling data with the functions that use it, and controlling accessShowing only what an object does, not how it does itPreventing direct access to data
FocusStructure: how code is packagedDesign: what the user needs to knowProtection of the data itself
How in C++Classes with private data and public member functionsAbstract classes, pure virtual functions, clean public interfacesThe private and protected specifiers
RelationshipUses data hiding as its toolOften achieved through encapsulationOne part of encapsulation
Bank exampleBalance and deposit/withdraw kept together in one classThe user sees “withdraw money”, not the ledger logicbalance is private

Benefits of encapsulation

  • Valid data: every change goes through checks, so objects cannot enter an impossible state.
  • Easier maintenance: the internal representation can change (say, storing paise instead of rupees) without breaking any code that uses the class.
  • Fewer bugs: there is one place to look when a value goes wrong.
  • Modularity: teams can work on different classes through agreed interfaces.
  • Read-only or write-only members: supply only a getter, or only a setter.

Common interview and exam questions on encapsulation

  • Is a struct in C++ encapsulated? It can be. A C++ struct can have private members and member functions; only its default access is public.
  • Does a friend function break encapsulation? It widens access to one named function or class, which the class itself chooses to grant. Used sparingly, it is part of the class’s design rather than a leak.
  • Is encapsulation the same as data hiding? No. Data hiding is one part of it; encapsulation also includes bundling the data with its operations.
  • Why not just make everything public and be careful? Because the compiler then cannot stop anyone, including you six months later, from bypassing the rules.

The Standard C++ Foundation site (isocpp.org) has further reading on classes and good C++ design.

FAQs

What is the meaning of encapsulation in C++?

Encapsulation in C++ means putting data members and the member functions that work on them together in a class, and making the data private so that outside code can only reach it through the class’s public functions.

How is encapsulation achieved in C++?

By declaring data members private (or protected) and providing public member functions, such as getters, setters and operations like deposit(), that read or change the data after checking it.

Is encapsulation possible in C?

Yes. Declare an incomplete struct type in the header, define its fields only in the .c file, expose functions that take a pointer to it, and mark internal helper functions static. Outside code then cannot access the fields.

What is the difference between encapsulation and abstraction?

Encapsulation packages data with its functions and controls access to them. Abstraction hides the implementation and shows only what an object does. Encapsulation is one of the main ways abstraction is achieved.

Why are class members private by default in C++?

So that encapsulation is the default. A member is hidden unless the programmer deliberately makes it public, which reduces accidental exposure of internal data.

Related Topics on EngineeringHulk

Advertisement