Programming Language & Its Applications β C++ Constructs with Objects and Classes, NEC licence examination syllabus (Nepal Engineering Council).
The function that runs automatically when an object dies β and the other half of RAII.
finally block and no garbage collector. A std::lock_guard locks a mutex in its constructor and unlocks it in its destructor, so the lock is released even if an exception is thrown mid-function β something a Java synchronized block also gives you, but C++ generalises it to every resource: files, sockets, database transactions, GPU buffers. Python's with statement is the same idea done explicitly; C++ makes it automatic. Search "RAII lock_guard exception safety" to see why this pattern eliminated an entire family of resource-leak bugs.#include <iostream>
#include <string>
class Tracer {
std::string name;
public:
Tracer(std::string n) : name(std::move(n)) {
std::cout << " + " << name << " born\n";
}
~Tracer() { std::cout << " - " << name << " dies\n"; }
};
Tracer global("global");
void func() {
std::cout << " enter func\n";
Tracer local("local");
{
Tracer inner("inner-block");
} /* inner dies HERE */
std::cout << " leave func\n";
} /* local dies HERE */
int main() {
std::cout << "main starts\n";
func();
Tracer *heap = new Tracer("heap");
std::cout << "before delete\n";
delete heap; /* dies exactly here */
Tracer a("a"), b("b"), c("c");
std::cout << "main ends\n";
return 0; /* c, b, a β REVERSE order */
}
Output:
+ global born
main starts
enter func
+ local born
+ inner-block born
- inner-block dies
leave func
- local dies
+ heap born
before delete
- heap dies
+ a born
+ b born
+ c born
main ends
- c dies
- b dies
- a dies
- global dies
#include <iostream>
#include <cstdio>
#include <stdexcept>
/* THE MANUAL WAY β every exit path must release */
void manual_bad(bool fail) {
FILE *fp = std::fopen("data.txt", "w");
if (!fp) return;
std::fprintf(fp, "some work\n");
if (fail) {
/* forgot fclose -> LEAK. And if we THROW instead
of returning, fclose is skipped even if written. */
throw std::runtime_error("failed mid-way");
}
std::fclose(fp);
}
/* THE RAII WAY β the destructor cannot be skipped */
class FileGuard {
FILE *fp;
public:
FileGuard(const char *name, const char *mode)
: fp(std::fopen(name, mode)) {
if (!fp) throw std::runtime_error("cannot open");
std::cout << " opened\n";
}
~FileGuard() { if (fp) { std::fclose(fp);
std::cout << " closed\n"; } }
/* a resource holder must not be copied naively */
FileGuard(const FileGuard&) = delete;
FileGuard& operator=(const FileGuard&) = delete;
FILE *get() { return fp; }
};
void raii_good(bool fail) {
FileGuard f("data.txt", "w");
std::fprintf(f.get(), "some work\n");
if (fail) throw std::runtime_error("failed mid-way");
std::cout << " finished normally\n";
}
int main() {
std::cout << "normal path:\n";
raii_good(false);
std::cout << "exception path:\n";
try { raii_good(true); }
catch (const std::exception &e) {
std::cout << " caught: " << e.what() << "\n";
}
return 0;
}
Output:
normal path:
opened
finished normally
closed
exception path:
opened
closed <-- destructor ran during unwinding
caught: failed mid-way
= delete on the copy constructor is not decoration. A FileGuard copied naively would have two objects holding the same FILE*, and both destructors would fclose it β a double-close, the same shape of bug as double-free. Deleting the copy operations makes the mistake a compile error instead of a runtime crash.
#include <iostream>
class Part {
const char *n;
public:
Part(const char *name) : n(name) {
std::cout << " +Part " << n << "\n";
}
~Part() { std::cout << " -Part " << n << "\n"; }
};
class Engine {
Part block{"block"}; /* declared 1st */
Part piston{"piston"}; /* declared 2nd */
Part valve{"valve"}; /* declared 3rd */
public:
Engine() { std::cout << " +Engine body\n"; }
~Engine() { std::cout << " -Engine body\n"; }
};
int main() {
std::cout << "creating Engine:\n";
{ Engine e; std::cout << " ...using engine...\n"; }
std::cout << "done\n";
return 0;
}
Output:
creating Engine:
+Part block
+Part piston
+Part valve
+Engine body
...using engine...
-Engine body
-Part valve
-Part piston
-Part block
done
#include <iostream>
#include <memory>
class Node { public: ~Node() { std::cout << "~Node "; } };
int main() {
/* delete[] calls EVERY destructor */
Node *arr = new Node[3];
std::cout << "delete[]: ";
delete[] arr;
std::cout << "\n";
/* safe delete idiom */
Node *p = new Node;
std::cout << "delete p: ";
delete p; p = nullptr;
delete p; /* legal no-op on nullptr */
std::cout << "\n";
/* the modern answer: no delete at all */
{ auto up = std::make_unique<Node>();
std::cout << "unique_ptr scope end: "; }
std::cout << "\n";
return 0;
}
Output:
delete[]: ~Node ~Node ~Node
delete p: ~Node
unique_ptr scope end: ~Node
Three destructor calls from delete[] β with plain delete you would see one, and the other two objects would never be destroyed. For int that leaks nothing visible; for objects holding files or memory, it leaks everything they own.
using bolted on later. Search "why C++ does not need a garbage collector" and "deterministic destruction" β the argument is stronger than it first sounds.~, no parameters, no return type, exactly one per class, cannot be overloaded) and list when it is called for locals, temporaries, heap objects and globals. The construction/destruction order β members in declaration order, destroyed in reverse, body last on the way in and first on the way out β is a favourite trace question. Know the five common mistakes, especially delete versus delete[] and the non-virtual base destructor leak.Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Loadingβ¦