C++ Object-Oriented Design: Encapsulation, Construction, and Operator Overloading
Encapsulation and Access Control
C++ encapsulation restricts direct access to internal state, exposing only validated interfaces. By default, class members are private, whereas struct members are public. Access specifiers (private, protected, public) define visibility boundaries, with protected becoming significant during inheritance hierarchies.
class InventoryItem {
std::string sku;
int quantity;
public:
InventoryItem(std::string code, int qty)
: sku(code), quantity(qty) {}
bool adjustStock(int delta) {
if (quantity + delta >= 0) {
quantity += delta;
return true;
}
return false;
}
int getStock() const { return quantity; }
};
Data validation logic inside member functions prevents invalid states. External code cannot modify quantity directly, ensuring the inventory never becomes negative.
Construction and Destruction Lifecycle
Constructors initialize objects automatically upon instantiation. Destructors clean up resources when objects leave scope or are deleted.
class NetworkConnection {
int socketFd;
std::string endpoint;
public:
NetworkConnection(std::string addr) : endpoint(addr), socketFd(-1) {
socketFd = createSocket(addr);
}
~NetworkConnection() {
if (socketFd >= 0) close(socketFd);
}
};
Automatic storage duration objects invoke destructors when exiting their block; static and global objects clean up after main() completes. Temporary (anonymous) objects execute constructors immediately and destructors at the end of the full expression:
PacketBuffer(4096); // Anonymous object: constructs and destructs on this line
Copy Semantics
The compiler generates a default copy constructor performing member-wise copying. For pointer members, this creates shallow copies where multiple objects reference identical heap addresses.
class Document {
char* content;
size_t length;
public:
Document(const char* text) {
length = strlen(text);
content = new char[length + 1];
strcpy(content, text);
}
// Deep copy constructor
Document(const Document& other) {
length = other.length;
content = new char[length + 1];
memcpy(content, other.content, length + 1);
}
~Document() { delete[] content; }
};
Deep copying allocates independent memory, preventing double-free errors during destruction. Copy construction triggers during value parameter passing and function returns:
void archiveDocument(Document doc); // Copy constructor invoked here
Document fetchDocument(); // Return value optimization may elide copy
Member Initialization Order
When a class contains other class objects, construction follows declaration order, not initialization list order. Destruction proceeds in reverse:
class SystemModule {
DatabaseConnection db;
CacheLayer cache;
// Construction: db first, then cache
// Destruction: cache first, then db
};
Friend Functions and Classes
Friends bypass encapsulation for specific external functions or classes without exposing internals globally.
Global Friand Functions
class SecureWallet {
double balance;
int securityCode;
public:
SecureWallet(double initial, int code)
: balance(initial), securityCode(code) {}
friend bool verifyIntegrity(const SecureWallet& wallet, int auth);
};
bool verifyIntegrity(const SecureWallet& wallet, int auth) {
return (auth == 9999) && (wallet.balance >= 0);
}
Friend Classes
When Auditor requires unrestricted access to SecureWallet:
class SecureWallet; // Forward declaration
class Auditor {
public:
void inspect(const SecureWallet& w);
};
class SecureWallet {
double balance;
friend class Auditor;
public:
SecureWallet(double amount) : balance(amount) {}
};
void Auditor::inspect(const SecureWallet& w) {
std::cout << "Balance: " << w.balance << std::endl;
}
Member Function Friends
Specific member functions of one class may become friends of another:
class Transaction;
class Account {
double funds;
friend void Transaction::commit(Account& acc);
};
Operator Overloading
Custom types utilize operators through explicit definitions, improving syntax clarity while maintaining type safety.
Arithmetic Operators
class Velocity {
double vx, vy;
public:
Velocity(double x, double y) : vx(x), vy(y) {}
Velocity operator+(const Velocity& other) const {
return Velocity(vx + other.vx, vy + other.vy);
}
friend Velocity operator*(double scalar, const Velocity& v) {
return Velocity(v.vx * scalar, v.vy * scalar);
}
};
Stream Insertion Operator
Stream operators require non-member implementation to position the stream object on the left:
class Telemetry {
std::string sensorId;
double reading;
public:
Telemetry(std::string id, double val)
: sensorId(id), reading(val) {}
friend std::ostream& operator<<(std::ostream& os, const Telemetry& t) {
os << "[" << t.sensorId << "] " << t.reading << " units";
return os;
}
};
Increment Operators
Distinguish prefix and postfix using a dummy enteger parameter:
class StepCounter {
unsigned steps;
public:
StepCounter() : steps(0) {}
// Prefix increment: return reference
StepCounter& operator++() {
++steps;
return *this;
}
// Postfix increment: return previous value
StepCounter operator++(int) {
StepCounter previous(*this);
++steps;
return previous;
}
};
Assignment Operators
Assignment requires handling self-assignment and resource management:
class ImageBuffer {
uint8_t* pixels;
size_t dimensions;
public:
ImageBuffer& operator=(const ImageBuffer& other) {
if (this != &other) { // Self-assignment guard
delete[] pixels;
dimensions = other.dimensions;
pixels = new uint8_t[dimensions];
std::copy(other.pixels, other.pixels + dimensions, pixels);
}
return *this;
}
};
Relational Operators
bool operator==(const Velocity& lhs, const Velocity& rhs) {
return (lhs.vx == rhs.vx) && (lhs.vy == rhs.vy);
}
bool operator!=(const Velocity& lhs, const Velocity& rhs) {
return !(lhs == rhs);
}
Function Call Operator
Functors overload operator() creating callable objects maintaining state:
class LinearMapper {
double slope;
double intercept;
public:
LinearMapper(double m, double b) : slope(m), intercept(b) {}
double operator()(double input) const {
return slope * input + intercept;
}
};
// Usage
LinearMapper convert(9.0/5.0, 32); // Celsius to Fahrenheit
double tempF = convert(100.0); // Returns 212.0
// Anonymous functor
int result = LinearMapper(2, 10)(5); // Returns 20, object destroyed immediately