इनहेरिटेंस और Is-A संबंध
public इनहेरिटेंस, कंस्ट्रक्टर चेनिंग, स्लाइसिंग की समस्या, मल्टीपल इनहेरिटेंस और डायमंड प्रॉब्लम सीखें — और यह भी कि इनहेरिटेंस कब उचित है।
इनहेरिटेंस Is-A संबंध दर्शाता है
Public इनहेरिटेंस “is-a” (है-एक) संबंध दर्शाता है: Dog एक Animal है, SavingsAccount एक BankAccount है। यह लिस्कोव सब्स्टिट्यूशन प्रिंसिपल (Liskov Substitution Principle) (LSP) से संचालित होता है: जहाँ भी बेस क्लास अपेक्षित हो, वहाँ डिराइव्ड क्लास बिना किसी अप्रत्याशित व्यवहार के सही ढंग से काम करनी चाहिए।
इनहेरिटेंस OOP की सबसे शक्तिशाली — और सबसे ज़्यादा गलत इस्तेमाल होने वाली — सुविधाओं में से एक है। हर संबंध “is-a” नहीं होता। Stack एक Vector नहीं है, भले ही वह अंदर से उसका उपयोग करता हो। जहाँ कंपोज़िशन उचित हो वहाँ इनहेरिटेंस का गलत उपयोग करने से कोड नाज़ुक और बनाए रखने में कठिन हो जाता है।
बुनियादी इनहेरिटेंस और कंस्ट्रक्टर चेनिंग
डिराइव्ड क्लास बेस क्लास के सभी मेंबर इनहेरिट करती है। बेस क्लास का कंस्ट्रक्टर पहले चलता है, फिर डिराइव्ड क्लास का कंस्ट्रक्टर। विनाश उलटे क्रम में होता है।
#include <iostream>
#include <string>
class Animal {
std::string name_;
int age_;
public:
Animal(const std::string& name, int age)
: name_{name}, age_{age}
{
std::cout << "Animal constructed: " << name_ << '\n';
}
virtual ~Animal() {
std::cout << "Animal destroyed: " << name_ << '\n';
}
const std::string& name() const { return name_; }
int age() const { return age_; }
virtual void speak() const {
std::cout << name_ << " makes a sound\n";
}
};
class Dog : public Animal { // public inheritance = is-a
std::string breed_;
public:
Dog(const std::string& name, int age, const std::string& breed)
: Animal{name, age} // base class constructor MUST be called first
, breed_{breed}
{
std::cout << "Dog constructed: " << name() << '\n';
}
~Dog() override {
std::cout << "Dog destroyed: " << name() << '\n';
}
void speak() const override {
std::cout << name() << " barks! (" << breed_ << ")\n";
}
};
int main() {
Dog rex{"Rex", 5, "German Shepherd"};
rex.speak(); // Rex barks! (German Shepherd)
// Destruction order: Dog destroyed, then Animal destroyed
return 0;
}स्लाइसिंग की समस्या
ऑब्जेक्ट स्लाइसिंग (object slicing) तब होती है जब डिराइव्ड ऑब्जेक्ट को किसी बेस क्लास वेरिएबल में असाइन किया जाता है या वैल्यू से पास किया जाता है। डिराइव्ड हिस्सा “काट दिया जाता है” — केवल बेस वाला हिस्सा बचता है। यह एक सूक्ष्म और आम बग है।
#include <iostream>
#include <string>
#include <vector>
#include <memory>
class Shape {
public:
virtual ~Shape() = default;
virtual std::string type() const { return "Shape"; }
};
class Circle : public Shape {
double radius_;
public:
explicit Circle(double r) : radius_{r} {}
std::string type() const override { return "Circle"; }
};
// BUG: passing by value slices the object!
void print_type_bad(Shape s) {
std::cout << s.type() << '\n'; // always prints "Shape"!
}
// CORRECT: pass by reference to preserve polymorphism
void print_type_good(const Shape& s) {
std::cout << s.type() << '\n'; // prints actual type
}
int main() {
Circle c{5.0};
print_type_bad(c); // "Shape" — SLICED!
print_type_good(c); // "Circle" — correct
// Slicing also happens with containers of values:
// std::vector<Shape> shapes; // BAD: slicing on insert
// Use pointers instead:
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(3.0));
std::cout << shapes[0]->type() << '\n'; // "Circle"
return 0;
}मल्टीपल इनहेरिटेंस और डायमंड प्रॉब्लम
C++ एक क्लास को कई बेस से इनहेरिट करने देता है। डायमंड प्रॉब्लम (diamond problem) तब होती है जब दो बेस का कोई साझा पूर्वज (ancestor) हो, जिससे डिराइव्ड क्लास में पूर्वज के डेटा की दो कॉपी आ जाती हैं। वर्चुअल इनहेरिटेंस (virtual inheritance) इसे हल करता है।
#include <iostream>
// Diamond problem:
// Device
// / \
// Printer Scanner
// \ /
// AllInOne
class Device {
protected:
int id_;
public:
Device(int id) : id_{id} {
std::cout << "Device(" << id_ << ")\n";
}
virtual ~Device() = default;
};
// Virtual inheritance: only ONE copy of Device in AllInOne
class Printer : virtual public Device {
public:
Printer(int id) : Device{id} {
std::cout << "Printer\n";
}
void print_doc() { std::cout << "Printing on device " << id_ << '\n'; }
};
class Scanner : virtual public Device {
public:
Scanner(int id) : Device{id} {
std::cout << "Scanner\n";
}
void scan_doc() { std::cout << "Scanning on device " << id_ << '\n'; }
};
class AllInOne : public Printer, public Scanner {
public:
// With virtual inheritance, the most-derived class
// must initialize the virtual base directly
AllInOne(int id) : Device{id}, Printer{id}, Scanner{id} {
std::cout << "AllInOne\n";
}
};
int main() {
AllInOne aio{42};
aio.print_doc(); // Printing on device 42
aio.scan_doc(); // Scanning on device 42
// Only ONE Device sub-object exists — no ambiguity
return 0;
}कोवेरिएंट रिटर्न टाइप और प्रोटेक्टेड एक्सेस
कोवेरिएंट रिटर्न टाइप (covariant return types) डिराइव्ड क्लास को वर्चुअल फ़ंक्शन ओवरराइड करने और अधिक विशिष्ट (डिराइव्ड) पॉइंटर या रेफ़रेंस टाइप रिटर्न करने देते हैं। उदाहरण के लिए, अगर Base::clone() Base* रिटर्न करता है, तो Derived::clone() Derived* रिटर्न कर सकता है।
protected एक्सेस स्पेसिफ़ायर public और private के बीच आता है: protected मेंबर क्लास स्वयं और उसकी डिराइव्ड क्लास के लिए सुलभ होते हैं, बाहरी कोड के लिए नहीं। जिन मेंबर की डिराइव्ड क्लास को वाजिब ज़रूरत हो, उनके लिए protected इस्तेमाल करें — लेकिन सावधान रहें, क्योंकि यह बेस और डिराइव्ड के बीच कसा हुआ कपलिंग (coupling) पैदा करता है।
एक आम गलती है केवल कोड दोबारा इस्तेमाल करने के लिए इनहेरिटेंस का उपयोग करना। अगर Stack, std::vector से इनहेरिट करे, तो उपयोगकर्ता push_back, insert और दूसरे वेक्टर ऑपरेशन कॉल कर सकते हैं जो स्टैक के ऐब्स्ट्रैक्शन का उल्लंघन करते हैं। इनहेरिटेंस को सच्चा is-a संबंध दर्शाना चाहिए जो लिस्कोव सब्स्टिट्यूशन प्रिंसिपल को पूरा करता हो। कोड रीयूज़ के लिए कंपोज़िशन (composition) को प्राथमिकता दें (ऐसा Stack जो vector को *रखता* है)।
हमेशा public इनहेरिटेंस इस्तेमाल करें — private/protected इनहेरिटेंस की ज़रूरत कम ही पड़ती है और वह भ्रमित करने वाला है। बेस क्लास के डिस्ट्रक्टर को हमेशा virtual डिक्लेयर करें। पॉलीमॉर्फ़िक ऑब्जेक्ट को रेफ़रेंस या पॉइंटर से पास करें, कभी वैल्यू से नहीं (स्लाइसिंग से बचने के लिए)। जब तक संबंध सचमुच “is-a” न हो, इनहेरिटेंस की बजाय कंपोज़िशन को प्राथमिकता दें। गहरी इनहेरिटेंस हाइरार्की से बचें (व्यावहारिक सीमा आमतौर पर 2-3 स्तर है)।
- Public इनहेरिटेंस is-a दर्शाता है — डिराइव्ड क्लास को लिस्कोव सब्स्टिट्यूशन प्रिंसिपल पूरा करना चाहिए
- बेस कंस्ट्रक्टर पहले चलते हैं, डिस्ट्रक्टर आख़िर में (निर्माण के उलटे क्रम में)
- ऑब्जेक्ट स्लाइसिंग तब होती है जब डिराइव्ड ऑब्जेक्ट बेस में वैल्यू से पास किया जाए — हमेशा रेफ़रेंस या पॉइंटर इस्तेमाल करें
- डायमंड प्रॉब्लम
virtualइनहेरिटेंस से हल होती है, लेकिन बेहतर है कि कंपोज़िशन को प्राथमिकता दें - इनहेरिटेंस सब्स्टिट्यूटेबिलिटी के लिए है, कोड रीयूज़ के लिए नहीं — उसके लिए कंपोज़िशन इस्तेमाल करें
क्विज़ — अपना ज्ञान परखें
(15 XP)1. ऑब्जेक्ट स्लाइसिंग क्या है?
2. वर्चुअल इनहेरिटेंस डायमंड प्रॉब्लम को कैसे हल करता है?
3. `Stack` क्लास बनाने के लिए `std::vector` से इनहेरिट क्यों नहीं करना चाहिए?