oops-05-classes-friends-buggy-code.md

Classes, Objects, Friend Functions and Buggy Code Snippets

Priority: MEDIUM — likely as warm-up questions and "spot the bug" exercises Spoken answer target: under 45 seconds each


1. Class

Definition:

A class is a user-defined blueprint or template that defines the data (attributes) and behavior (methods) of objects. It is a logical construct — it occupies no memory by itself.

Key Point:

  • Data members are private by default (vs struct: public by default)
  • Class defines WHAT the object looks like; the object IS an instance at runtime
  • In C++, class and struct differ only in default access: class = private, struct = public
class Dog { private: string name; string breed; public: Dog(string n, string b) : name(n), breed(b) {} void bark() { cout << name << " says: Woof!"; } string getName() const { return name; } };

Likely follow-up:

"Struct vs Class in C++?" The ONLY technical difference is default access: struct members are public by default, class members are private. Convention: use struct for plain data holders (no behavior, no encapsulation), class for objects with behavior and protected state.


2. Object

Definition:

An object is an instance of a class — a concrete realization of the blueprint that occupies actual memory. Each object has its own copy of data members but shares the class's methods.

Key Point:

  • Stack: fast, automatic cleanup (RAII), limited size
  • Heap (new): flexible lifetime, must explicitly delete (or use smart pointers)
  • Modern C++: prefer unique_ptr over raw new/delete
// Stack allocation — automatic cleanup when scope ends Dog d1("Buddy", "Labrador"); d1.bark(); // Heap allocation — manual (dangerous — prefer smart pointers!) Dog* d2 = new Dog("Max", "Shepherd"); d2->bark(); delete d2; // Must manually free! // Smart pointer — automatic cleanup, no manual delete auto d3 = make_unique<Dog>("Rex", "Husky"); d3->bark(); // d3 auto-deleted when it goes out of scope

Likely follow-up:

"What is RAII?" Resource Acquisition Is Initialization — tie a resource's lifetime to an object's lifetime. Acquire in constructor, release in destructor. Destructors run automatically when scope ends, so resources cannot leak — even if exceptions are thrown. unique_ptr, lock_guard, fstream are all RAII types.


3. Friend Function

Definition:

A friend function is a non-member function (or another class) that is explicitly granted access to the private and protected members of a class by declaring it inside the class with the friend keyword.

Key Point:

  • Friend is explicitly granted — the class remains in control
  • Does NOT break encapsulation — it extends it in a controlled way
  • Most common use: operator overloading (especially <<, >>, and symmetric operators like 3 * vec)
class BankAccount { private: double balance; string owner; public: BankAccount(string o, double b) : owner(o), balance(b) {} // Declare friend — grants access to private members friend void audit(const BankAccount& acc); friend class Auditor; // friend needed for << because ostream is left operand friend ostream& operator<<(ostream& os, const BankAccount& acc) { os << acc.owner << ": $" << acc.balance; // Accesses private directly! return os; } }; // This function is NOT a member — but can access private fields void audit(const BankAccount& acc) { cout << "Audit: " << acc.owner << " has $" << acc.balance; // ^^^^^^^^^^^^^^ private — OK via friend! } int main() { BankAccount acc("Alice", 1000); audit(acc); // Non-member accessing private cout << acc << endl; // friend operator<< }

Likely follow-up:

"Is friend breaking encapsulation?" No — the class EXPLICITLY grants access. Only specific functions/classes are friends. If anything, it is a deliberate, documented extension of trust — not a bypass.

Common mistake: Overusing friend. If you are making a dozen unrelated classes friends, you have lost the point of encapsulation. Friend should be reserved for tightly coupled pairs (like a class and its operator<< implementation).


4. Getters and Setters — Encapsulation Link

Definition:

Getters (accessors) and setters (mutators) are public methods that provide controlled read/write access to private data members. The key is the VALIDATION and SIDE EFFECTS they can add.

Key Point: A getter/setter pair without validation is just a struct with extra steps — no real encapsulation. Real value comes from: validation, computed properties, triggering side effects (logging, notifications, caching), and the ability to change internal representation later without breaking callers.

class Temperature { private: double celsius; public: // Getter — read-only access double getCelsius() const { return celsius; } // Computed property — derived from internal state double getFahrenheit() const { return celsius * 9.0 / 5.0 + 32; } // Setter WITH VALIDATION void setCelsius(double value) { if (value < -273.15) throw invalid_argument("Below absolute zero!"); celsius = value; } // Setter that reuses validation logic void setFahrenheit(double f) { setCelsius((f - 32) * 5.0 / 9.0); // Converts then calls validated setter } };

BAD example (anemic class — no real encapsulation):

class User { string name; string email; public: string getName() { return name; } void setName(string n) { name = n; } // No validation string getEmail() { return email; } void setEmail(string e) { email = e; } // No validation // This is just a struct in disguise! };

5. Buggy Code Snippets — Spot the Bug (Interview Favorites)


Bug 1: Missing virtual — Polymorphism Silently Broken

class Base { public: void speak() { cout << "Base"; } // BUG: not virtual! }; class Derived : public Base { public: void speak() { cout << "Derived"; } }; int main() { Base* p = new Derived(); p->speak(); // Outputs "Base" -- NOT "Derived"! delete p; }

Bug: speak() is not virtual. Through a Base pointer, static binding always calls Base::speak. No dynamic dispatch happens. Fix: Add virtual to Base::speak(). Also add override in Derived.


Bug 2: Missing Virtual Destructor — Memory Leak

class Base { public: ~Base() { cout << "Base destroyed"; } // BUG: not virtual! }; class Derived : public Base { int* data = new int[100]; public: ~Derived() { delete[] data; // This NEVER runs! cout << "Derived destroyed"; } }; int main() { Base* p = new Derived(); delete p; // Only ~Base() runs. ~Derived() is skipped. MEMORY LEAK! }

Bug: Non-virtual destructor. delete p calls Base's destructor only. Derived's destructor (which frees data) is never called. Fix: virtual ~Base() { ... }. Rule: any class with virtual functions MUST have a virtual destructor.


Bug 3: Shallow Copy — Double Delete Crash

class Resource { int* data; int size; public: Resource(int s) : size(s), data(new int[s]) {} // BUG: No copy constructor defined! // Compiler generates a SHALLOW copy — both objects share same pointer! ~Resource() { delete[] data; } // Both r1 and r2 will try to delete the same pointer! }; int main() { Resource r1(10); Resource r2 = r1; // Shallow copy! r1.data == r2.data // r2 destroyed: delete[] data -- OK // r1 destroyed: delete[] data -- CRASH! Already freed! Double-free undefined behavior. }

Bug: Compiler-generated copy constructor does shallow copy. Both objects share the same raw pointer. Double-delete on destruction. Fix: Write a deep copy constructor: Resource(const Resource& other) : size(other.size), data(new int[other.size]) { ... copy values ... }


Bug 4: Calling Virtual Function in Constructor

class Base { public: Base() { init(); // BUG: virtual dispatch does NOT work in constructors! } virtual void init() { cout << "Base init"; } }; class Derived : public Base { public: void init() override { cout << "Derived init"; } }; int main() { Derived d; // Output: "Base init" — NOT "Derived init"! }

Bug: During Base's constructor execution, the Derived part of the object has not been constructed yet. The vptr points to Base's vtable, so init() resolves to Base::init() always. Fix: Don't call virtual functions in constructors or destructors. Initialize in the constructor body using non-virtual calls, or use a factory method.


Bug 5: Object Slicing — Losing Derived Data

void makeSpeak(Animal a) { // BUG: pass by VALUE — slicing! a.speak(); // Always calls Animal::speak, even if you pass a Dog } int main() { Dog d; makeSpeak(d); // "..." — Dog's override is LOST }

Bug: The function takes Animal by value. When d is passed, the Dog portion (including the vtable pointer and breed) is sliced off. Only the Animal part is copied. Fix: void makeSpeak(const Animal& a) — pass by reference (or pointer) to preserve polymorphism.


Bug 6: Diamond Problem Without Virtual Inheritance

class A { public: int x = 10; }; class B : public A {}; // BUG: should be virtual public A class C : public A {}; // BUG: should be virtual public A class D : public B, public C {}; int main() { D d; d.x = 5; // ERROR: ambiguous! d.B::A::x or d.C::A::x? // Workaround (ugly): d.B::x = 5; d.C::x = 5; // Two separate copies! }

Bug: Without virtual inheritance, B and C each have their own copy of A. D has two copies, making d.x ambiguous. Fix: class B : virtual public A and class C : virtual public A. Then D must initialize A directly: D() : A() {}.


Bug 7: Overload Ambiguity with Default Arguments

void print(int x, int y = 0) { cout << "A"; } void print(int x) { cout << "B"; } int main() { print(5); // ERROR: ambiguous! Both match! print(5, 0); // OK — only first overload matches }

Bug: print(5) could match both print(int) and print(int, int=0) since the default argument makes y optional. Compiler cannot resolve this. Fix: Remove one of the overloads, or change the default argument so they are no longer ambiguous.


Bug 8: Using Moved-From Object

int main() { string s1 = "hello"; string s2 = move(s1); // s1 is now in a valid but empty state cout << s1; // BUG: undefined behavior! s1 has been moved from. // In practice with std::string: outputs "" (empty), but relying on this is wrong. s1 = "world"; // OK — reassigning a moved-from object is safe cout << s1; // "world" }

Bug: Using s1 after it has been moved. The moved-from object is in a valid but unspecified state. You should only destroy or reassign it. Fix: Do not access a moved-from object. If you must reuse it, reassign it first.


Quick Self-Test — Classes, Friends, Bugs

  1. What is the only difference between class and struct in C++?
  2. What is a friend function — does it break encapsulation?
  3. Why should operator<< be a friend (or non-member)?
  4. What is object slicing and how do you prevent it?
  5. What is the bug when a virtual function is called in a constructor?
  6. What causes a double-free crash in C++?
  7. What is the correct signature for post-increment operator++?
  8. Can a function be a friend of multiple classes?
  9. What is the difference between passing by value and by reference for polymorphic objects?
  10. After string s2 = move(s1), what is the state of s1?

Answers

  1. Default access: struct = public, class = private. That is the ONLY difference.
  2. A non-member function explicitly granted access to private members. It does NOT break encapsulation — the class grants it intentionally.
  3. The left operand of << is ostream, not your class. Member functions require your class as the left operand. So it must be a free function (friend to access private data).
  4. Derived-specific data is lost when a derived object is copied into a base object by value. Prevent with pointers or references.
  5. During construction, the vptr points to the current class's vtable (not derived). Virtual dispatch resolves to the base version, not the override.
  6. Shallow copy — two objects share the same raw pointer. When both are destroyed, delete is called twice on the same memory.
  7. ClassName operator++(int) — takes a dummy int parameter to distinguish from pre-increment.
  8. Yes — a function can be declared as friend inside multiple class declarations.
  9. By value causes slicing — only the base portion is copied and polymorphism is lost. By reference or pointer preserves the actual type and enables virtual dispatch.
  10. Valid but unspecified state. With std::string it is usually empty. You should only destroy or reassign it — do NOT read from it.