C++ Access Specifiers
Introduction to Access Specifiers
Access specifiers in C++ determine how class members (data and functions) can be accessed from different parts of a program. They are a key feature for implementing encapsulation, which helps keep the internal details of a class hidden and only exposes what is necessary.
C++ provides three main access specifiers: `public`, `private`, and `protected`. Choosing the appropriate one helps ensure clarity, safety, and proper use of class interfaces.
Access Specifier Overview
Specifier | Access From Inside Class | Access From Derived Class | Access From Outside Class |
---|---|---|---|
public | Yes | Yes | Yes |
protected | Yes | Yes | No |
private | Yes | No | No |
Public Members
Public members can be accessed from anywhere: inside the class, in derived classes, and from outside the class using an object.
They are commonly used for functions that form the public interface of a class and for constants that clients of the class should be able to use.
#include <iostream>
using namespace std;
class MyClass {
public:
int x;
void show() {
cout << "x = " << x << endl;
}
};
int main() {
MyClass obj;
obj.x = 10; // Allowed
obj.show(); // Allowed
return 0;
}
x = 10
Private Members
Private members can only be accessed from within the class in which they are declared. They cannot be directly accessed from derived classes or external code.
They are typically used for implementation details and for protecting the internal state of an object from unintended modification.
#include <iostream>
using namespace std;
class MyClass {
private:
int secret;
public:
void setSecret(int s) {
secret = s; // Allowed inside the class
}
void reveal() {
cout << "Secret: " << secret << endl;
}
};
int main() {
MyClass obj;
// obj.secret = 42; // ❌ Error: not accessible
obj.setSecret(42);
obj.reveal();
return 0;
}
Secret: 42
Protected Members
Protected members are similar to private members but can also be accessed by derived classes.
Important nuance: a derived class can access a base class's protected member generally only through its own subobject (i.e., via `*this` or a further-derived object). It cannot use this access to reach the protected member of an arbitrary, unrelated `Base` object.
#include <iostream>
using namespace std;
class Base {
protected:
int value;
public:
void setValue(int v) { value = v; }
};
class Derived : public Base {
public:
void showOwnValue() {
cout << "own value: " << value << endl; // ✅ Allowed (accessing own subobject)
}
void tryOtherBase(Base& b) {
// cout << b.value; // ❌ Not allowed: cannot access protected member of an unrelated Base object
(void)b;
}
};
int main() {
Derived d;
d.setValue(100);
d.showOwnValue();
// d.value = 200; // ❌ Error: protected in Base when accessed from outside
return 0;
}
own value: 100
Default Access Levels
By default, members of a `class` are private, while members of a `struct` are public.
If you omit the access specifier, C++ uses the default rules depending on whether you declare a class or a struct.
Default inheritance access (separate from member access): `class Derived : Base` (no keyword) means private inheritance by default; `struct Derived : Base` means public inheritance by default.
class MyClass {
int x; // private by default
};
struct MyStruct {
int y; // public by default
};
struct PubBase { public: void api(); };
class D1 : PubBase { /* private inheritance by default: PubBase::api becomes private via D1's interface */ };
struct D2 : PubBase { /* public inheritance by default: PubBase::api remains public via D2's interface */ };
Friendship (Access Bypass)
`friend` declarations grant access to `private` and `protected` members of a class to specific functions or entire classes. Friendship is not inherited and is not transitive.
#include <iostream>
using namespace std;
class SecretBox {
private:
int secret = 7;
friend void peek(const SecretBox&);
};
void peek(const SecretBox& b) { // friend function
cout << "peek: " << b.secret << endl; // ✅ Allowed due to friendship
}
int main() {
SecretBox sb;
peek(sb);
return 0;
}
peek: 7
Inheritance Access vs Member Access
Member access (`public`/`protected`/`private` on members) governs where a member can be named. Inheritance access (`public`/`protected`/`private` after the colon) controls how base-class members appear through the derived class interface to outside code. It does not affect what the derived class itself can use internally.
Summary when inheriting from a base: with public inheritance, base public stays public and base protected stays protected (base private is inaccessible). With protected inheritance, base public and protected both become protected. With private inheritance, base public and protected both become private. In all cases, base private remains inaccessible to the derived class.
Inheritance Mode | Base public becomes | Base protected becomes | Base private |
---|---|---|---|
public | public | protected | inaccessible |
protected | protected | protected | inaccessible |
private | private | private | inaccessible |
Best Practices
- Prefer private data members to preserve encapsulation and reduce dependencies on internal implementation.
- Use public access for methods that form the external interface of a class.
- Use protected access only when derived classes truly need it; avoid overusing it to prevent tight coupling.
- For simple data structures, `struct` with public members may be appropriate.
- Be cautious when modifying access levels in base classes, as it can affect all derived classes.
- Prefer composition over private inheritance unless you intentionally want an "is-implemented-in-terms-of" relationship and control the derived interface.
Summary Table
Specifier | Typical Use |
---|---|
public | Methods and constants forming the external interface. |
protected | Members available to derived classes but hidden from outside code. |
private | Internal data and helper functions, hidden from all external access. |