ఎక్సెప్షన్ హ్యాండ్లింగ్, ఫైల్ I/O & గ్రాండ్ కాప్స్టోన్ ప్రాజెక్ట్ (Exceptions, Files & Capstone)
std::exception, std::runtime_error) · Stack Unwinding & RAII · Custom Exception Classes · File I/O (std::ifstream, std::ofstream) · Binary vs Text Modes · Serialization · గ్రాండ్ ఫినాలే: పూర్తి C++ రియల్-వరల్డ్ బ్యాంకింగ్ & ఆడిట్ లెడ్జర్ ప్రాజెక్ట్!
try-catch-throw ఆర్కిటెక్చర్, Stack Unwinding మరియు RAII మెకానిజమ్స్ మీ సిస్టమ్ను బుల్లెట్ప్రూఫ్గా మారుస్తాయి. దీనితో పాటు, మీ అప్లికేషన్ డేటాను డిస్క్లో శాశ్వతంగా భద్రపరచడానికి ఫైల్ స్ట్రీమ్స్ (Text & Binary File I/O) ఎలా ఉపయోగించాలో నేర్చుకుంటాం.
చివరిగా, ఈ 20 అధ్యాయాల సమగ్ర C++ ప్రయాణానికి కిరీటంలా — ఆబ్జెక్ట్ ఓరియెంటెడ్ పాలిమార్ఫిజమ్, STL కంటైనర్లు, స్మార్ట్ పాయింటర్లు, కస్టమ్ ఎక్సెప్షన్స్ మరియు డిస్క్ పర్సిస్టెన్స్ కలగలిసిన పూర్తి స్థాయి ఎంటర్ప్రైజ్ బ్యాంకింగ్ & ట్రాన్సాక్షన్ ఆడిట్ లెడ్జర్ ప్రాజెక్ట్ ను మీ చేతులతో నిర్మిస్తారు!
1 ఎక్సెప్షన్ హ్యాండ్లింగ్ ఎందుకు? (Why Exceptions vs Traditional Error Codes)
ఒక సాఫ్ట్వేర్ రన్ అవుతున్నప్పుడు రెండు రకాల లోపాలు ఎదురవుతాయి:
- కంపైల్ టైమ్ ఎర్రర్స్ (Compile-time Errors): సింటాక్స్ మిస్టేక్స్, టైప్ మిస్మ్యాచ్లు. వీటిని కంపైలర్ బిల్డ్ దశలోనే పట్టి ఆపుతుంది.
- రన్టైమ్ ఎర్రర్స్ (Run-time Anomalies / Exceptions): ఫైల్ మిస్ అవ్వడం, జీరోతో డివైడ్ చేయడం, నెట్వర్క్ టైమ్అవుట్, యూజర్ నెగటివ్ బ్యాలెన్స్ ఎంటర్ చేయడం. వీటిని ప్రోగ్రామ్ రన్ అవుతున్న సమయంలో డైనమిక్గా హ్యాండిల్ చేయాలి.
సాంప్రదాయ C లాంగ్వేజ్లో ప్రతి ఫంక్షన్ ఒక ఇంటిజర్ ఎర్రర్ కోడ్ (ఉదాహరణకు 0 సక్సెస్, -1 ఎర్రర్) రిటర్న్ చేస్తుంది. దీనివల్ల డీప్ కాల్ స్టాక్ ఉన్నప్పుడు (Function A -> Function B -> Function C -> Function D), ప్రతి లెవెల్ వద్ద పదుల సంఖ్యలో if (err != 0) return err; చెక్స్ రాయాల్సి వస్తుంది. దీనినే "Spaghetti Error Handling" అంటారు.
2 try, catch, throw ఫండమెంటల్స్ & కంట్రోల్ ఫ్లో
C++ ఎక్సెప్షన్ హ్యాండ్లింగ్ మూడు కీలక కీవర్డ్లపై ఆధారపడి పనిచేస్తుంది:
try: రిస్క్ లేదా ఎర్రర్ వచ్చే అవకాశం ఉన్న కోడ్ బ్లాక్నుtry { ... }లో ఉంచుతాం.throw: ఏదైనా ఊహించని తప్పు జరిగినప్పుడు, ఆ సమాచారాన్ని ఒక ఆబ్జెక్ట్ లేదా వేల్యూ రూపంలో బయటకు విసిరివేయడానికి (signal an anomaly)throwఉపయోగిస్తాం.catch: విసిరివేయబడిన ఎక్సెప్షన్ను పట్టుకుని, సమస్యను పరిష్కరించడానికి లేదా యూజర్కు అర్థమయ్యేలా లాగ్ చేయడానికిcatch (ExceptionType e) { ... }ఉపయోగిస్తాం.
#include <iostream>
#include <stdexcept> // standard exception classes
// సురక్షితమైన డివిజన్ ఫంక్షన్
double safeDivide(double numerator, double denominator) {
if (denominator == 0.0) {
// జీరోతో భాగించడం అసాధ్యం, కాబట్టి runtime_error ను త్రో చేస్తున్నాం
throw std::runtime_error("గణిత లోపం: సున్నాతో భాగించడం సాధ్యం కాదు (Divide by Zero)!");
}
return numerator / denominator;
}
int main() {
std::cout << "--- Modern C++ ఎక్సెప్షన్ హ్యాండ్లింగ్ డిమో ---\n";
double a = 100.0;
double b = 0.0;
try {
std::cout << "డివిజన్ ఆపరేషన్ ప్రారంభమవుతోంది...\n";
double result = safeDivide(a, b);
// ఎక్సెప్షన్ వస్తే క్రింది లైన్ ఎగ్జిక్యూట్ అవ్వదు!
std::cout << "ఫలితం: " << result << "\n";
}
catch (const std::runtime_error& ex) {
// సురక్షితంగా ఎక్సెప్షన్ను పట్టుకున్నాం
std::cerr << "🚨 [ERROR CAUGHT]: " << ex.what() << "\n";
std::cout << "ℹ️ రికవరీ లాజిక్: డిఫాల్ట్ వేల్యూ 0.0 కేటాయించబడింది.\n";
}
std::cout << "✅ ప్రోగ్రామ్ సురక్షితంగా తన ప్రయాణాన్ని కొనసాగిస్తోంది!\n";
return 0;
}
3 C++ స్టాండర్డ్ ఎక్సెప్షన్ హైరార్కీ (Standard Exception Hierarchy)
C++ స్టాండర్డ్ లైబ్రరీ (<stdexcept>) ఒక క్రమబద్ధమైన క్లాస్ హైరార్కీని అందిస్తుంది. ఈ క్లాసులన్నీ మూల పేరెంట్ అయిన std::exception నుండి ఇన్హెరిట్ అవుతాయి.
ప్రతి ఎక్సెప్షన్ క్లాస్లో virtual const char* what() const noexcept; అనే మెథడ్ ఉంటుంది, ఇది ఎర్రర్ వివరణాత్మక మెసేజ్ను రిటర్న్ చేస్తుంది.
| Exception Class | రకం / కేటగిరీ | ఎప్పుడు సంభవిస్తుంది? (Real-world Scenario) |
|---|---|---|
std::exception |
Root Base Class | C++ లోని అన్ని స్టాండర్డ్ ఎక్సెప్షన్లకు మూల క్లాస్. పాలిమార్ఫిక్ క్యాచ్ కోసం వాడుతారు. |
std::logic_error |
ప్రోగ్రామర్ లాజిక్ లోపం | కోడ్ రాయడంలో తప్పుల వల్ల సంభవించేవి (ఉదాహరణకు ప్రీ-కండిషన్స్ ఫెయిల్ అవ్వడం). |
std::invalid_argument |
logic_error డెరివేటివ్ | ఫంక్షన్కు చెల్లని ఆర్గ్యుమెంట్ పంపినప్పుడు (ఉదా: నెగటివ్ ఏజ్, తప్పు ఇమెయిల్ ఫార్మాట్). |
std::out_of_range |
logic_error డెరివేటివ్ | కంటైనర్ పరిధి దాటి యాక్సెస్ చేసినప్పుడు (ఉదా: std::vector::at(99) బౌండ్స్ ఎర్రర్). |
std::runtime_error |
సిస్టమ్ / రన్టైమ్ లోపం | రన్టైమ్లో సిస్టమ్ లేదా ఎక్స్టర్నల్ ఈవెంట్ల వల్ల జరిగే లోపాలు (ఫైల్ దొరకకపోవడం, హార్డ్వేర్ ఫెయిల్యూర్). |
std::bad_alloc |
మెమరీ లోపం (<new>) |
హీప్ మెమరీ పూర్తిగా అయిపోయి new ఆపరేటర్ ఫెయిల్ అయినప్పుడు. |
4 Stack Unwinding & RAII గ్యారెంటీ (Destructor Cleanup)
C++ ఎక్సెప్షన్ సిస్టమ్ యొక్క అత్యున్నత సూపర్-పవర్ Stack Unwinding. ఒక ఫంక్షన్ లోపల throw జరిగినప్పుడు, దానికి సరిపడే catch బ్లాక్ దొరికే వరకు కాల్ స్టాక్లో వెనుకకు వెళ్తుంది.
ఈ వెనుకకు వెళ్ళే క్రమంలో స్టాక్ ఫ్రేమ్లలో క్రియేట్ చేయబడిన ప్రతి లోకల్ ఆబ్జెక్ట్ యొక్క Destructor ఆటోమేటిక్గా ఎగ్జిక్యూట్ అవుతుంది!
#include <iostream>
#include <string>
#include <stdexcept>
// RAII రిసోర్స్ క్లాస్ - డిస్ట్రక్టర్ ఎగ్జిక్యూషన్ ట్రాక్ చేయడానికి
class NetworkSocket {
std::string endpoint;
public:
NetworkSocket(const std::string& ep) : endpoint(ep) {
std::cout << " 🔌 [OPEN] సాకెట్ కనెక్షన్ తెరవబడింది: " << endpoint << "\n";
}
~NetworkSocket() {
std::cout << " 🧹 [CLEANUP] Stack Unwinding ద్వారా సాకెట్ క్లోజ్ చేయబడింది: " << endpoint << "\n";
}
};
void deepFunction() {
NetworkSocket sock("192.168.1.100:8080");
std::cout << " ⚡ deepFunction లోపల కీలక డేటా ప్రాసెస్ అవుతోంది...\n";
// ఏదో తప్పు జరిగి ఎక్సెప్షన్ త్రో అయింది
throw std::runtime_error("నెట్వర్క్ ప్యాకెట్ డ్రాప్ అయ్యింది (Corrupted Payload)!");
std::cout << " ఈ లైన్ ఎప్పటికీ ప్రింట్ అవ్వదు!\n";
}
int main() {
std::cout << "=== STACK UNWINDING & RAII సేఫ్టీ డెమో ===\n";
try {
deepFunction();
}
catch (const std::exception& e) {
std::cout << "🚨 [CATCH BLOCK]: ప్రధాన హ్యాండ్లర్ లో ఎర్రర్ దొరికింది: " << e.what() << "\n";
}
std::cout << "✅ ఏ మెమరీ లీక్ లేకుండా సిస్టమ్ రికవర్ అయ్యింది!\n";
return 0;
}
5 Catch by Reference & Object Slicing నివారణ
ఎక్సెప్షన్ హ్యాండ్లింగ్లో బిగినర్స్ చేసే అతిపెద్ద తప్పు: ఎక్సెప్షన్ను పాస్-బై-వేల్యూ (catch (std::exception e)) తో క్యాచ్ చేయడం.
ఎల్లప్పుడూ కాన్స్ట్ రిఫరెన్స్ (catch (const std::exception& e)) ద్వారానే క్యాచ్ చేయాలి. ఎందుకు?
- ఆబ్జెక్ట్ స్లైసింగ్ (Object Slicing) నివారణ: బై-వేల్యూ క్యాచ్ చేస్తే, మీరు త్రో చేసిన డెరైవ్డ్ క్లాస్ (ఉదా:
class MyCustomError : public std::runtime_error) బేస్ క్లాస్ లోకి స్లైస్ అయిపోతుంది. దీనివల్ల డెరైవ్డ్ క్లాస్లోని ఓవర్రైడ్ చేయబడినwhat()లేదా కస్టమ్ ఫీల్డ్స్ కోల్పోతారు. - అనవసరపు కాపీ ఓవర్హెడ్ నివారణ: కాన్స్ట్ రిఫరెన్స్ వల్ల కొత్త కాపీ ఆబ్జెక్ట్ తయారుకాదు, పెర్ఫార్మెన్స్ వేగవంతంగా ఉంటుంది.
- మల్టిపుల్ క్యాచ్ బ్లాక్స్ రూల్: స్పెసిఫిక్ డెరైవ్డ్ ఎక్సెప్షన్లను పైన, జనరిక్ బేస్ ఎక్సెప్షన్లను క్రింద పెట్టాలి.
catch (...)(క్యాచ్-ఆల్) ను ఎల్లప్పుడూ చివరిలో ఉంచాలి.
6 కస్టమ్ ఎక్సెప్షన్ క్లాసెస్ నిర్మాణం (Custom Exception Classes)
రియల్ వరల్డ్ ప్రాజెక్ట్లలో జనరిక్ ఎర్రర్ల బదులు మీ బిజినెస్ డొమైన్కు సరిపడే కస్టమ్ ఎక్సెప్షన్ క్లాసులు రాయడం ఎంతో ప్రొఫెషనల్ పద్ధతి.
దీనికోసం std::runtime_error లేదా std::exception నుండి ఇన్హెరిట్ చేసుకుంటాం.
#include <iostream>
#include <string>
#include <stdexcept>
// బ్యాంకింగ్ డొమైన్ కోసం రూట్ బేస్ ఎక్సెప్షన్
class BankingException : public std::runtime_error {
public:
explicit BankingException(const std::string& msg) : std::runtime_error(msg) {}
};
// అకౌంట్లో తగినంత నిధులు లేనప్పుడు వచ్చే కస్టమ్ ఎక్సెప్షన్
class InsufficientFundsException : public BankingException {
double currentBalance;
double requestedAmount;
public:
InsufficientFundsException(double balance, double requested)
: BankingException("ఖాతాలో సరిపడా నిధులు లేవు! బ్యాలెన్స్: " + std::to_string(balance) +
", ఉపసంహరించదలచిన మొత్తం: " + std::to_string(requested)),
currentBalance(balance), requestedAmount(requested) {}
double getDeficit() const noexcept { return requestedAmount - currentBalance; }
};
class BankAccount {
std::string accNumber;
double balance;
public:
BankAccount(const std::string& acc, double initial) : accNumber(acc), balance(initial) {}
void withdraw(double amount) {
if (amount > balance) {
throw InsufficientFundsException(balance, amount);
}
balance -= amount;
std::cout << "✅ ఉపసంహరణ విజయవంతం! విత్డ్రా: ₹ " << amount << ", ప్రస్తుత నిల్వ: ₹ " << balance << "\n";
}
};
int main() {
BankAccount myAcc("SB-987654321", 5000.0);
try {
std::cout << "లావాదేవీ 1: ₹ 2000 విత్డ్రా చేయడానికి ప్రయత్నిస్తున్నాం...\n";
myAcc.withdraw(2000.0);
std::cout << "\nలావాదేవీ 2: ₹ 7000 విత్డ్రా చేయడానికి ప్రయత్నిస్తున్నాం...\n";
myAcc.withdraw(7000.0);
}
catch (const InsufficientFundsException& ex) {
std::cerr << "❌ [BANKING ERROR]: " << ex.what() << "\n";
std::cerr << " లక్ష్యం నెరవేరడానికి ఇంకా లోటు మొత్తం: ₹ " << ex.getDeficit() << "\n";
}
catch (const BankingException& ex) {
std::cerr << "❌ [GENERAL BANK ERROR]: " << ex.what() << "\n";
}
return 0;
}
7 noexcept స్పెసిఫైయర్ & మూవ్ సెమాంటిక్స్ పెర్ఫార్మెన్స్
Modern C++ (C++11 నుండి) లో ఒక ఫంక్షన్ ఎప్పటికీ ఎక్సెప్షన్ను త్రో చేయదు అని కంపైలర్కు హామీ ఇవ్వడానికి noexcept కీవర్డ్ వాడతాం.
- ఒకవేళ
noexceptఅని మార్క్ చేసిన ఫంక్షన్ నుండి ఏదైనా ఎక్సెప్షన్ త్రో అయితే, సిస్టమ్ అన్-వైండింగ్ చేయకుండా తక్షణమేstd::terminate()ను పిలిచి ప్రోగ్రామ్ను ఆపేస్తుంది. - STL పెర్ఫార్మెన్స్ బూస్ట్:
std::vectorవంటి కంటైనర్లు రీ-సైజ్ అయ్యేటప్పుడు, మీ క్లాస్ యొక్క మూవ్ కన్స్ట్రక్టర్noexceptఅని ఉంటేనే Move చేస్తుంది; లేకపోతే సేఫ్టీ కోసం ఖరీదైన డీప్ కాపీ (Copy Constructor) ని రన్ చేస్తుంది! - డిస్ట్రక్టర్లు బై-డిఫాల్ట్
noexcept(true): C++ లో డిస్ట్రక్టర్ల లోపల ఎప్పుడూ ఎక్సెప్షన్ త్రో చేయకూడదు. డిస్ట్రక్టర్ నుండి ఎక్సెప్షన్ బయటకు వస్తే అప్లికేషన్ వెంటనే క్రాష్ అవుతుంది.
8 ఫైల్ I/O పరిచయం: <fstream> స్ట్రీమ్ ఆర్కిటెక్చర్
రామ్ (RAM) లో ఉండే డేటా ప్రోగ్రామ్ ముగియగానే లేదా కరెంట్ పోగానే అంతరించిపోతుంది (Volatile). కానీ శాశ్వత స్టోరేజ్ (SSD/HDD) పై డేటాను సేవ్ చేయడానికి C++ లో <fstream> హెడర్ లైబ్రరీని ఉపయోగిస్తాం.
| Stream Class | దిశ (Direction) | పనితీరు & ప్రయోజనం |
|---|---|---|
std::ofstream |
Output File Stream (రాయడం) | ఫైల్ లోకి డేటాను వ్రాయడానికి (Writing). ఫైల్ లేకపోతే కొత్త ఫైల్ సృష్టిస్తుంది. |
std::ifstream |
Input File Stream (చదవడం) | డిస్క్లోని ఫైల్ నుండి డేటాను చదవడానికి (Reading). ఫైల్ లేకపోతే ఓపెన్ అవ్వదు. |
std::fstream |
Bi-directional Stream | ఒకే ఫైల్ పై చదవడం (Read) మరియు రాయడం (Write) రెండూ చేయడానికి. |
ముఖ్యమైన ఫైల్ ఓపెన్ మోడ్స్ (Open Modes):
std::ios::in— చదవడానికి మాత్రమే తెరవడం (ifstream డిఫాల్ట్).std::ios::out— రాయడానికి తెరవడం; ఉన్న డేటాను క్లియర్ చేస్తుంది (ofstream డిఫాల్ట్).std::ios::app— అపెండ్ మోడ్; ఫైల్ చివరలో కొత్త డేటాను జోడిస్తుంది.std::ios::binary— బైనరీ మోడ్; న్యూలైన్ ట్రాన్స్లేషన్ లేకుండా రా బైట్స్ను చదవడం/రాయడం.
9 టెక్స్ట్ ఫైల్స్ రాయడం & చదవడం (Reading & Writing Text Files)
టెక్స్ట్ ఫైల్స్తో పనిచేసేటప్పుడు << ఆపరేటర్తో రాయడం, మరియు std::getline() ఫంక్షన్తో లైన్ల వారీగా చదవడం అత్యంత సాధారణ పద్ధతి.
ఫైల్ ఆబ్జెక్ట్ స్కోప్ దాటగానే RAII సూత్రం ప్రకారం ఆటోమేటిక్గా ఫైల్ క్లోజ్ అవుతుంది.
#include <iostream>
#include <fstream>
#include <string>
void writeAuditLog(const std::string& filename) {
// ofstream ఆబ్జెక్ట్ సృష్టించి ఫైల్ ఓపెన్ చేస్తున్నాం
std::ofstream outFile(filename, std::ios::out | std::ios::app);
if (!outFile.is_open()) {
std::cerr << "ఫైల్ తెరవడంలో లోపం ఏర్పడింది!\n";
return;
}
outFile << "TIMESTAMP: 2026-11-01 10:30:00 | USER: Admin | EVENT: Login Successful\n";
outFile << "TIMESTAMP: 2026-11-01 10:32:15 | USER: User_42 | EVENT: Fund Transfer ₹ 5000\n";
outFile << "TIMESTAMP: 2026-11-01 10:35:40 | USER: Admin | EVENT: Password Updated\n";
std::cout << "✅ ఆడిట్ లాగ్ డేటా విజయవంతంగా ఫైల్ లోకి వ్రాయబడింది!\n";
// outFile ఇక్కడ స్కోప్ దాటగానే ఆటోమేటిక్గా క్లోజ్ అవుతుంది (RAII)
}
void readAuditLog(const std::string& filename) {
std::ifstream inFile(filename);
if (!inFile.is_open()) {
std::cerr << "ఫైల్ చదవడానికి అందుబాటులో లేదు: " << filename << "\n";
return;
}
std::cout << "\n--- [" << filename << "] నుండి లైన్ల వారీగా డేటా రీడింగ్ ---\n";
std::string line;
int lineNum = 1;
while (std::getline(inFile, line)) {
std::cout << "లైన్ " << lineNum++ << ": " << line << "\n";
}
}
int main() {
std::string path = "system_audit.log";
writeAuditLog(path);
readAuditLog(path);
return 0;
}
10 బైనరీ ఫైల్ I/O & డేటా స్ట్రక్చర్ సీరియలైజేషన్ (Binary Serialization)
టెక్స్ట్ ఫైల్స్ మనుషులు చదవడానికి బాగుంటాయి కానీ ఎక్కువ స్పేస్ తీసుకుంటాయి మరియు నంబర్లను స్ట్రింగ్స్గా మార్చడానికి CPU సైకిల్స్ వృధా అవుతాయి. గేమ్ సేవ్ ఫైల్స్, డేటాబేస్ ఇండెక్స్ ఫైల్స్, హై-స్పీడ్ బ్యాంకింగ్ సిస్టమ్స్లో డేటాను యథాతథంగా మెమరీ బైట్స్ రూపంలో సేవ్ చేయడానికి Binary File I/O వాడతాం.
write(reinterpret_cast<const char*>(&obj), sizeof(obj))— ఆబ్జెక్ట్ బైట్లను నేరుగా డిస్క్కు రాయడం.read(reinterpret_cast<char*>(&obj), sizeof(obj))— డిస్క్ బైట్లను నేరుగా ఆబ్జెక్ట్ మెమరీలోకి నింపడం.
#include <iostream>
#include <fstream>
#include <cstring>
// ఫిక్స్డ్ సైజ్ ఉన్న POD (Plain Old Data) స్ట్రక్చర్
struct AccountRecord {
int accountId;
char customerName[32];
double balance;
bool isActive;
};
void saveBinaryRecord(const std::string& filename, const AccountRecord& record) {
std::ofstream out(filename, std::ios::binary);
if (!out) {
std::cerr << "బైనరీ ఫైల్ తెరవడం విఫలమైంది!\n";
return;
}
// మెమరీలోని బైట్లను నేరుగా డిస్క్పై వ్రాయడం
out.write(reinterpret_cast<const char*>(&record), sizeof(AccountRecord));
std::cout << "💾 బైనరీ రికార్డ్ సేవ్ చేయబడింది (" << sizeof(AccountRecord) << " బైట్స్).\n";
}
void loadBinaryRecord(const std::string& filename) {
std::ifstream in(filename, std::ios::binary);
if (!in) {
std::cerr << "బైనరీ ఫైల్ చదవడం విఫలమైంది!\n";
return;
}
AccountRecord loadedRecord{};
in.read(reinterpret_cast<char*>(&loadedRecord), sizeof(AccountRecord));
std::cout << "\n🔍 [డిస్క్ నుండి లోడ్ చేయబడిన రికార్డ్]:\n";
std::cout << " • అకౌంట్ ID: " << loadedRecord.accountId << "\n";
std::cout << " • ఖాతాదారుని పేరు: " << loadedRecord.customerName << "\n";
std::cout << " • బ్యాలెన్స్: ₹ " << loadedRecord.balance << "\n";
std::cout << " • యాక్టివ్ స్టేటస్: " << (loadedRecord.isActive ? "ACTIVE" : "INACTIVE") << "\n";
}
int main() {
AccountRecord user1{101, "రాజేష్ శర్మ", 85400.50, true};
std::string binFile = "account_101.dat";
saveBinaryRecord(binFile, user1);
loadBinaryRecord(binFile);
return 0;
}
11 ఫైల్ పాయింటర్లు & ర్యాండమ్ యాక్సెస్ (File Pointers & Random Access)
ఫైల్ను క్రమపద్ధతిలో మొదటి నుండి చివరి వరకు చదవడమే కాకుండా, కావలసిన నిర్దిష్ట లొకేషన్కు నేరుగా దూకడానికి ఫైల్ పాయింటర్లు (File Pointers) ఉపయోగపడతాయి:
- Get Pointer (చదవడానికి):
seekg(offset, direction)మూవ్ చేయడానికి,tellg()ప్రస్తుత స్థానం తెలుసుకోవడానికి. - Put Pointer (రాయడానికి):
seekp(offset, direction)మూవ్ చేయడానికి,tellp()ప్రస్తుత స్థానం తెలుసుకోవడానికి. - దిశలు (Directions):
std::ios::beg(ప్రారంభం నుండి),std::ios::cur(ప్రస్తుత స్థానం నుండి),std::ios::end(చివర నుండి).
12 కామన్ మిస్టేక్స్ & ప్రొడక్షన్ బెస్ట్ ప్రాక్టీసెస్ (Gotchas & Best Practices)
ఎక్సెప్షన్స్ మరియు ఫైల్ హ్యాండ్లింగ్లో ప్రొడక్షన్ సిస్టమ్స్ బ్రేక్ అవ్వకుండా పాటించాల్సిన గోల్డెన్ రూల్స్:
- Mistake 1: Empty Catch Blocks (Swallowing Exceptions):
catch (...) {}అని రాసి ఏమీ చేయకపోతే ఎర్రర్ సైలెంట్గా మాయమైపోతుంది, తర్వాత సిస్టమ్ క్రాష్ అయినప్పుడు రూట్ కాజ్ కనిపెట్టడం అసాధ్యం. కనీసం లాగ్ చేయండి! - Mistake 2: Throwing inside Destructors: డిస్ట్రక్టర్ నుండి ఎక్సెప్షన్ బయటకు వస్తే C++ రన్టైమ్ వెంటనే
std::terminate()పిలుస్తుంది. డిస్ట్రక్టర్ల లోపల ఎల్లప్పుడూtry-catchపెట్టి లోపలే హ్యాండిల్ చేయాలి. - Mistake 3: Serializing Pointers to Binary Files: బైనరీ ఫైల్స్ లోకి
std::stringలేదా పాయింటర్లు (e.g.int* ptr) ఉన్న క్లాస్లనుreinterpret_castద్వారా రాయకూడదు. ఎందుకంటే ఫైల్ లోకి వర్చువల్ మెమరీ అడ్రస్ మాత్రమే సేవ్ అవుతుంది, మళ్ళీ లోడ్ చేసినప్పుడు ఆ అడ్రస్ ఇన్వాలిడ్ అయి క్రాష్ అవుతుంది! - Mistake 4: Not checking
is_open(): ఫైల్ పాత్ తప్పుగా ఉన్నా లేదా పర్మిషన్ లేకపోయినా స్ట్రీమ్ ఫెయిల్ అవుతుంది. డేటా రాసేముందు ఎల్లప్పుడూfile.is_open()చెక్ చేయాలి.
13 గ్రాండ్ ఫినాలే రియల్-వరల్డ్ కాప్స్టోన్ ప్రాజెక్ట్: Enterprise Bank & Transaction Audit Ledger System
ఈ 20-అధ్యాయాల C++ మాస్టర్క్లాస్ యొక్క అత్యున్నత శిఖరం ఈ కాప్స్టోన్ ప్రాజెక్ట్. ఇది ఒక సాధారణ డెమో కాదు — మొత్తం C++ కోర్సులో మీరు నేర్చుకున్న అన్ని అధునాతన కాన్సెప్టులను కలగలిపి రూపొందించిన ఒక కంప్లీట్ ప్రొడక్షన్-గ్రేడ్ బ్యాంకింగ్ & ఆడిట్ లెడ్జర్ ఇంజిన్!
- ఆబ్జెక్ట్ ఓరియెంటెడ్ పాలిమార్ఫిజమ్: Abstract Base Class
Account, వర్చువల్ మెథడ్స్,SavingsAccount&CheckingAccount. - కస్టమ్ డొమైన్ ఎక్సెప్షన్స్:
BankingException,InsufficientFundsException,InvalidTransactionException. - రెస్పాన్సిబుల్ RAII & స్మార్ట్ పాయింటర్లు: మెమరీ లీక్స్ లేకుండా
std::unique_ptrమరియుstd::shared_ptrమేనేజ్మెంట్. - STL Containers & Hash Maps: ఖాతాలను వేగంగా శోధించడానికి
std::unordered_map, లెడ్జర్ కోసంstd::vector. - ఫైల్ పర్సిస్టెన్స్ & ఆడిట్ లాగింగ్: ప్రతి లావాదేవీని టైమ్స్టాంప్తో డిస్క్లోని
bank_audit_ledger.txtలోకి రాయడం. - Modern C++20 ఫీచర్స్:
noexceptమూవ్ ఆపరేషన్లు,constexprలిమిట్స్, స్ట్రక్చర్డ్ బైండింగ్స్.
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <chrono>
#include <ctime>
#include <stdexcept>
// ==========================================
// 1. కస్టమ్ ఎక్సెప్షన్ హైరార్కీ (Domain Exceptions)
// ==========================================
class BankingException : public std::runtime_error {
public:
explicit BankingException(const std::string& msg) : std::runtime_error(msg) {}
};
class InsufficientFundsException : public BankingException {
public:
InsufficientFundsException(const std::string& acc, double balance, double req)
: BankingException("ఖాతా [" + acc + "] లో తగినంత నిధులు లేవు! అందుబాటులో ఉన్నది: ₹ " +
std::to_string(balance) + ", కోరినది: ₹ " + std::to_string(req)) {}
};
class InvalidAccountException : public BankingException {
public:
explicit InvalidAccountException(const std::string& acc)
: BankingException("ఖాతా సంఖ్య చెల్లదు లేదా బ్యాంక్ రికార్డులలో లేదు: " + acc) {}
};
// ==========================================
// 2. లావాదేవీ రికార్డ్ & డిస్క్ ఆడిటర్ (Audit Logger)
// ==========================================
struct Transaction {
std::string timestamp;
std::string accountId;
std::string type;
double amount;
double postBalance;
};
class AuditLogger {
std::string logFilePath;
public:
AuditLogger(const std::string& path) : logFilePath(path) {}
void log(const Transaction& tx) {
std::ofstream file(logFilePath, std::ios::app);
if (file.is_open()) {
file << tx.timestamp << " | ACC: " << std::setw(12) << tx.accountId
<< " | TYPE: " << std::setw(10) << tx.type
<< " | AMOUNT: ₹ " << std::fixed << std::setprecision(2) << std::setw(10) << tx.amount
<< " | BAL: ₹ " << std::setw(10) << tx.postBalance << "\n";
}
}
};
// ==========================================
// 3. అబ్స్ట్రాక్ట్ బేస్ క్లాస్: Account (OOP)
// ==========================================
class Account {
protected:
std::string accountId;
std::string holderName;
double balance;
public:
Account(std::string id, std::string name, double initialBal)
: accountId(std::move(id)), holderName(std::move(name)), balance(initialBal) {}
virtual ~Account() = default;
virtual void deposit(double amount) {
if (amount <= 0) throw BankingException("డిపాజిట్ మొత్తం ₹ 0 కంటే ఎక్కువగా ఉండాలి!");
balance += amount;
}
virtual void withdraw(double amount) = 0; // Pure Virtual Function
const std::string& getId() const noexcept { return accountId; }
const std::string& getName() const noexcept { return holderName; }
double getBalance() const noexcept { return balance; }
virtual std::string getAccountType() const = 0;
};
// ==========================================
// 4. డెరైవ్డ్ క్లాస్ 1: SavingsAccount (వడ్డీ సౌకర్యం)
// ==========================================
class SavingsAccount : public Account {
double interestRate;
public:
SavingsAccount(std::string id, std::string name, double bal, double rate = 4.5)
: Account(std::move(id), std::move(name), bal), interestRate(rate) {}
void withdraw(double amount) override {
if (amount <= 0) throw BankingException("విత్డ్రా మొత్తం ధనాత్మకంగా ఉండాలి!");
if (amount > balance) {
throw InsufficientFundsException(accountId, balance, amount);
}
balance -= amount;
}
std::string getAccountType() const override { return "SAVINGS (సేవింగ్స్)"; }
};
// ==========================================
// 5. డెరైవ్డ్ క్లాస్ 2: CheckingAccount (ఓవర్డ్రాఫ్ట్ సౌకర్యం)
// ==========================================
class CheckingAccount : public Account {
double overdraftLimit;
public:
CheckingAccount(std::string id, std::string name, double bal, double limit = 15000.0)
: Account(std::move(id), std::move(name), bal), overdraftLimit(limit) {}
void withdraw(double amount) override {
if (amount <= 0) throw BankingException("విత్డ్రా మొత్తం ధనాత్మకంగా ఉండాలి!");
if (amount > (balance + overdraftLimit)) {
throw InsufficientFundsException(accountId, balance + overdraftLimit, amount);
}
balance -= amount;
}
std::string getAccountType() const override { return "CURRENT/CHECKING (ఓవర్డ్రాఫ్ట్)"; }
};
// ==========================================
// 6. సెంట్రల్ బ్యాంక్ సిస్టమ్ మేనేజర్ (STL + Exceptions + File I/O)
// ==========================================
class EnterpriseBankSystem {
std::unordered_map<std::string, std::shared_ptr<Account>> accounts;
AuditLogger auditor;
std::string getCurrentTimestamp() {
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
char buf[32];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
return std::string(buf);
}
public:
EnterpriseBankSystem(const std::string& logFile) : auditor(logFile) {}
void openAccount(std::shared_ptr<Account> acc) {
std::string id = acc->getId();
accounts[id] = acc;
auditor.log({getCurrentTimestamp(), id, "OPEN_ACC", acc->getBalance(), acc->getBalance()});
std::cout << " 🎉 ఖాతా తెరవబడింది: [" << id << "] " << acc->getName()
<< " | టైప్: " << acc->getAccountType() << " | ప్రారంభ బ్యాలెన్స్: ₹ " << acc->getBalance() << "\n";
}
void processDeposit(const std::string& id, double amount) {
auto it = accounts.find(id);
if (it == accounts.end()) throw InvalidAccountException(id);
it->second->deposit(amount);
auditor.log({getCurrentTimestamp(), id, "DEPOSIT", amount, it->second->getBalance()});
std::cout << " 💰 డిపాజిట్ విజయవంతం: ACC [" << id << "] +₹ " << amount
<< " => కొత్త బ్యాలెన్స్: ₹ " << it->second->getBalance() << "\n";
}
void processWithdrawal(const std::string& id, double amount) {
auto it = accounts.find(id);
if (it == accounts.end()) throw InvalidAccountException(id);
it->second->withdraw(amount);
auditor.log({getCurrentTimestamp(), id, "WITHDRAW", amount, it->second->getBalance()});
std::cout << " 🏧 విత్డ్రా విజయవంతం: ACC [" << id << "] -₹ " << amount
<< " => కొత్త బ్యాలెన్స్: ₹ " << it->second->getBalance() << "\n";
}
void transferFunds(const std::string& fromId, const std::string& toId, double amount) {
std::cout << "\n 🔄 అకౌంట్ ట్రాన్స్ఫర్ మొదలైంది: [" << fromId << "] నుండి [" << toId << "] కి ₹ " << amount << "...\n";
auto fromIt = accounts.find(fromId);
auto toIt = accounts.find(toId);
if (fromIt == accounts.end()) throw InvalidAccountException(fromId);
if (toIt == accounts.end()) throw InvalidAccountException(toId);
// Transactional Safety: డెబిట్ సక్సెస్ అయితేనే క్రెడిట్ చేస్తాం!
fromIt->second->withdraw(amount);
try {
toIt->second->deposit(amount);
} catch (...) {
// రోల్బ్యాక్ మెకానిజమ్
fromIt->second->deposit(amount);
throw BankingException("ట్రాన్స్ఫర్ ఫెయిల్ అయ్యింది! నిధులు రోల్బ్యాక్ చేయబడ్డాయి.");
}
auditor.log({getCurrentTimestamp(), fromId, "XFER_OUT", amount, fromIt->second->getBalance()});
auditor.log({getCurrentTimestamp(), toId, "XFER_IN", amount, toIt->second->getBalance()});
std::cout << " ✅ నిధుల బదిలీ పూర్తయింది! ఆడిట్ లాగ్ రికార్డ్ అయ్యింది.\n";
}
void displayAllAccounts() const {
std::cout << "\n=========================================================================================\n";
std::cout << " 🏦 ఎంటర్ప్రైజ్ బ్యాంక్ - ఖాతాదారుల రియల్-టైమ్ సమ్మరీ\n";
std::cout << "=========================================================================================\n";
for (const auto& [id, acc] : accounts) {
std::cout << " ID: " << std::setw(12) << acc->getId()
<< " | పేరు: " << std::setw(18) << acc->getName()
<< " | టైప్: " << std::setw(22) << acc->getAccountType()
<< " | బ్యాలెన్స్: ₹ " << std::setw(10) << acc->getBalance() << "\n";
}
std::cout << "=========================================================================================\n";
}
};
int main() {
std::cout << "=========================================================================================\n";
std::cout << " 🚀 MODERN C++ గ్రాండ్ కాప్స్టోన్ ప్రాజెక్ట్: ENTERPRISE BANK AUDIT SYSTEM\n";
std::cout << "=========================================================================================\n\n";
EnterpriseBankSystem bank("enterprise_bank_audit.txt");
// 1. ఖాతాలు తెరవడం
bank.openAccount(std::make_shared<SavingsAccount>("SB-1001", "రాహుల్ వర్మ", 45000.0));
bank.openAccount(std::make_shared<CheckingAccount>("CA-2001", "టెక్-సొల్యూషన్స్ లిమిటెడ్", 120000.0, 50000.0));
bank.openAccount(std::make_shared<SavingsAccount>("SB-1002", "అనన్య రెడ్డి", 15000.0));
bank.displayAllAccounts();
// 2. సాధారణ విజయవంతమైన లావాదేవీలు
std::cout << "\n[లావాదేవీల ఎగ్జిక్యూషన్]:\n";
try {
bank.processDeposit("SB-1001", 10000.0);
bank.processWithdrawal("CA-2001", 25000.0);
bank.transferFunds("SB-1001", "SB-1002", 15000.0);
}
catch (const BankingException& e) {
std::cerr << "🚨 [ERROR]: " << e.what() << "\n";
}
// 3. ఎక్సెప్షన్ హ్యాండ్లింగ్ టెస్టింగ్ (నిధుల కొరత)
std::cout << "\n[ఎక్సెప్షన్ టెస్ట్ 1: ఖాతాలో ఉన్నదానికంటే ఎక్కువ విత్డ్రా చేసినప్పుడు]:\n";
try {
bank.processWithdrawal("SB-1002", 90000.0);
}
catch (const InsufficientFundsException& e) {
std::cerr << " 🚨 ఆపరేషన్ తిరస్కరించబడింది: " << e.what() << "\n";
}
// 4. ఎక్సెప్షన్ టెస్ట్ 2: లేని ఖాతాకు బదిలీ ప్రయత్నించినప్పుడు
std::cout << "\n[ఎక్సెప్షన్ టెస్ట్ 2: లేని అకౌంట్ నంబర్ ఇచ్చినప్పుడు]:\n";
try {
bank.transferFunds("SB-1001", "SB-9999_UNKNOWN", 500.0);
}
catch (const InvalidAccountException& e) {
std::cerr << " 🚨 ఆపరేషన్ తిరస్కరించబడింది: " << e.what() << "\n";
}
bank.displayAllAccounts();
std::cout << "\n🎯 కాప్స్టోన్ ప్రాజెక్ట్ సక్సెస్: అన్ని లావాదేవీలు 'enterprise_bank_audit.txt' లో ఆడిట్ అయ్యాయి!\n";
std::cout << "🏆 అభినందనలు! మీరు Modern C++ Masterclass కోర్సును విజయవంతంగా పూర్తి చేసారు! 🚀\n";
return 0;
}
★ ఈ అధ్యాయంపై ప్రాక్టీస్ క్విజ్ (Knowledge Check)
1. C++ లో Stack Unwinding సమయంలో లోకల్ ఆబ్జెక్టుల మెమరీ మరియు రిసోర్సెస్కి ఏమి జరుగుతుంది?
2. ఎక్సెప్షన్ను catch (const std::exception& e) కాకుండా catch (std::exception e) ద్వారా పట్టుకుంటే ఏమి ప్రమాదం?
what() మరియు కస్టమ్ డెరివేటివ్ డేటా పోతుంది. కాన్స్ట్ రిఫరెన్స్ (&) వాడితే పాలిమార్ఫిజమ్ భద్రంగా ఉంటుంది మరియు కాపీ ఓవర్హెడ్ ఉండదు.
3. Destructor లోపల throw చేయడం వల్ల Modern C++ లో ఎందుకు తీవ్రమైన ప్రమాదం జరుగుతుంది?
noexcept(true) గా పరిగణించబడతాయి. పైగా ఇప్పటికే ఒక ఎక్సెప్షన్ వల్ల Stack Unwinding జరుగుతుండగా, డిస్ట్రక్టర్ లోపల నుండి మరొక ఎక్సెప్షన్ త్రో అయితే రెండు అన్హ్యాండిల్డ్ ఎక్సెప్షన్స్ ఒకేసారి యాక్టివ్ అవుతాయి. అప్పుడు C++ రన్టైమ్ వెంటనే std::terminate() ను పిలిచి ప్రోగ్రామ్ను ఆపేస్తుంది. అందుకే డిస్ట్రక్టర్ల నుండి ఎక్సెప్షన్స్ ఎప్పుడూ బయటకు రాకూడదు.
4. std::vector కోసం Move Constructor కి noexcept స్పెసిఫైయర్ ఎందుకు చాలా కీలకం?
std::vector కెపాసిటీ నిండి కొత్త మెమరీ లోకి ఎలిమెంట్స్ను తరలించేటప్పుడు బలమైన ఎక్సెప్షన్ సేఫ్టీ (Strong Exception Guarantee) ని కాపాడాలి. ఒకవేళ మూవ్ కన్స్ట్రక్టర్ noexcept కాకపోతే, మధ్యలో ఎర్రర్ వస్తే పాత ఎలిమెంట్స్ చెదిరిపోతాయనే భయంతో వెక్టర్ మూవ్ చేయకుండా ప్రతి ఎలిమెంట్ను నెమ్మదిగా కాపీ (Copy Constructor) చేస్తుంది. noexcept ఇవ్వడం వల్లనే నిజమైన మూవ్ సెమాంటిక్స్ వేగవంతమైన స్పీడ్ అందుతుంది.
5. బైనరీ ఫైల్ I/O లో డైనమిక్ మెమరీ ఉన్న క్లాస్లను (ఉదా: std::string, std::vector) reinterpret_cast తో నేరుగా ఎందుకు రాయకూడదు?
std::string లేదా డైనమిక్ ఆబ్జెక్టుల లోపల హీప్ మెమరీని పాయింట్ చేసే పాయింటర్లు ఉంటాయి. reinterpret_cast కేవలం ఆబ్జెక్ట్ యొక్క స్టాక్ బైట్స్ను మాత్రమే రాస్తుంది (అంటే వర్చువల్ మెమరీ అడ్రస్లను). తర్వాతి సెషన్లో లేదా వేరే కంప్యూటర్లో ఆ ఫైల్ను చదివినప్పుడు ఆ పాయింటర్ అడ్రస్ చెల్లదు (Dangling pointer / Segfault). అందువల్ల కేవలం POD (Plain Old Data / Fixed Char Arrays) లేదా ప్రాపర్ సీరియలైజేషన్ ఫార్మాట్లను మాత్రమే బైనరీలో రాయాలి.
ఈ కాప్స్టోన్ ప్రాజెక్ట్ను లైవ్గా మన కంపైలర్లో రన్ చేయండి!
ఎటువంటి లోకల్ సెటప్ లేకుండా, బ్రౌజర్లోనే నేరుగా Modern C++ (GCC 13 / C++20) కంపైలర్లో ఈ పూర్తి స్థాయి ఎంటర్ప్రైజ్ బ్యాంకింగ్ సిస్టమ్ కోడ్ను ఎగ్జిక్యూట్ చేసి రన్టైమ్ అవుట్పుట్ను తనిఖీ చేయండి.