खोज…
बहुरूपी वर्गों को परिभाषित करें
विशिष्ट उदाहरण एक अमूर्त आकार वर्ग है, जिसे तब वर्गों, हलकों और अन्य ठोस आकृतियों में प्राप्त किया जा सकता है।
मूल वर्ग:
आइए बहुरूपी वर्ग से शुरू करें:
class Shape {
public:
virtual ~Shape() = default;
virtual double get_surface() const = 0;
virtual void describe_object() const { std::cout << "this is a shape" << std::endl; }
double get_doubled_surface() const { return 2 * get_surface(); }
};
इस परिभाषा को कैसे पढ़ें?
आप कीवर्ड
virtual
साथ शुरू किए गए सदस्य कार्यों द्वारा बहुरूपी व्यवहार को परिभाषित कर सकते हैं। यहाँget_surface()
औरdescribe_object()
स्पष्ट रूप से एक चक्र के लिए की तुलना में एक वर्ग के लिए अलग तरह से लागू किया जाएगा। जब फ़ंक्शन किसी ऑब्जेक्ट पर लगाया जाता है, तो रनटाइम पर ऑब्जेक्ट की वास्तविक कक्षा के अनुरूप कार्य निर्धारित किया जाएगा।यह सार आकार के लिए
get_surface()
को परिभाषित करने के लिए कोई मतलब नहीं है। यही कारण है कि फ़ंक्शन= 0
द्वारा पीछा किया जाता है। इसका मतलब है कि फ़ंक्शन शुद्ध वर्चुअल फ़ंक्शन है ।एक बहुरूपी वर्ग को हमेशा एक आभासी विध्वंसक को परिभाषित करना चाहिए।
आप गैर आभासी सदस्य कार्यों को परिभाषित कर सकते हैं। जब ये फ़ंक्शन किसी ऑब्जेक्ट के लिए आमंत्रित किया जाएगा, तो संकलन-समय पर उपयोग किए गए वर्ग के आधार पर फ़ंक्शन को चुना जाएगा। यहाँ
get_double_surface()
को इस तरह से परिभाषित किया गया है।एक वर्ग जिसमें कम से कम एक शुद्ध आभासी फ़ंक्शन होता है वह एक अमूर्त वर्ग होता है। अमूर्त वर्गों को तत्काल नहीं किया जा सकता है। आपके पास केवल एक अमूर्त वर्ग प्रकार के संकेत या संदर्भ हो सकते हैं।
व्युत्पन्न वर्ग
एक बार एक पॉलीमॉर्फिक बेस क्लास परिभाषित होने के बाद आप इसे प्राप्त कर सकते हैं। उदाहरण के लिए:
class Square : public Shape {
Point top_left;
double side_length;
public:
Square (const Point& top_left, double side)
: top_left(top_left), side_length(side_length) {}
double get_surface() override { return side_length * side_length; }
void describe_object() override {
std::cout << "this is a square starting at " << top_left.x << ", " << top_left.y
<< " with a length of " << side_length << std::endl;
}
};
कुछ स्पष्टीकरण:
- आप मूल वर्ग के किसी भी वर्चुअल फ़ंक्शन को परिभाषित या ओवरराइड कर सकते हैं। तथ्य यह है कि मूल कक्षा में एक फ़ंक्शन आभासी था, यह व्युत्पन्न वर्ग में आभासी बनाता है। कंपाइलर को कीवर्ड को फिर से
virtual
बताने की आवश्यकता नहीं है। लेकिन फ़ंक्शन के घोषणा के अंत में कीवर्डoverride
को जोड़ने की सिफारिश की जाती है, ताकि फ़ंक्शन हस्ताक्षर में अनपेक्षित बदलावों के कारण होने वाले सूक्ष्म कीड़े को रोका जा सके। - यदि मूल वर्ग के सभी शुद्ध आभासी कार्यों को परिभाषित किया जाता है, तो आप इस वर्ग के लिए वस्तुओं को तुरंत रोक सकते हैं, अन्यथा यह एक सार वर्ग भी बन जाएगा।
- आप सभी वर्चुअल फ़ंक्शंस को ओवरराइड करने के लिए बाध्य नहीं हैं। यदि आप अपनी जरूरत के अनुसार माता-पिता के संस्करण को रख सकते हैं।
तात्कालिकता का उदाहरण
int main() {
Square square(Point(10.0, 0.0), 6); // we know it's a square, the compiler also
square.describe_object();
std::cout << "Surface: " << square.get_surface() << std::endl;
Circle circle(Point(0.0, 0.0), 5);
Shape *ps = nullptr; // we don't know yet the real type of the object
ps = &circle; // it's a circle, but it could as well be a square
ps->describe_object();
std::cout << "Surface: " << ps->get_surface() << std::endl;
}
सुरक्षित डाउनकास्टिंग
मान लीजिए कि आपके पास एक पॉलीमॉर्फिक क्लास के ऑब्जेक्ट के लिए एक पॉइंटर है:
Shape *ps; // see example on defining a polymorphic class
ps = get_a_new_random_shape(); // if you don't have such a function yet, you
// could just write ps = new Square(0.0,0.0, 5);
डाउनकास्ट सामान्य पॉलिमॉर्फिक Shape
से लेकर उसके व्युत्पन्न और Square
या Circle
जैसे अधिक विशिष्ट आकार तक का होगा।
डाउनकास्ट क्यों करें?
अधिकांश समय, आपको यह जानने की आवश्यकता नहीं होगी कि वस्तु का वास्तविक प्रकार कौन सा है, क्योंकि वर्चुअल फ़ंक्शंस आपको स्वतंत्र रूप से अपने ऑब्जेक्ट को उसके प्रकार से हेरफेर करने की अनुमति देते हैं:
std::cout << "Surface: " << ps->get_surface() << std::endl;
यदि आपको किसी डाउनकास्ट की आवश्यकता नहीं है, तो आपका डिज़ाइन एकदम सही होगा।
हालांकि, आपको कभी-कभी डाउनकास्ट करने की आवश्यकता हो सकती है। एक विशिष्ट उदाहरण तब है जब आप एक गैर-आभासी फ़ंक्शन को लागू करना चाहते हैं जो केवल बाल वर्ग के लिए मौजूद है।
उदाहरण मंडलियों पर विचार करें। केवल मंडलियों का एक व्यास होता है। तो वर्ग को इस प्रकार परिभाषित किया जाएगा:
class Circle: public Shape { // for Shape, see example on defining a polymorphic class
Point center;
double radius;
public:
Circle (const Point& center, double radius)
: center(center), radius(radius) {}
double get_surface() const override { return r * r * M_PI; }
// this is only for circles. Makes no sense for other shapes
double get_diameter() const { return 2 * r; }
};
get_diameter()
सदस्य फ़ंक्शन केवल मंडलियों के लिए मौजूद हैं। इसे Shape
ऑब्जेक्ट के लिए परिभाषित नहीं किया गया था:
Shape* ps = get_any_shape();
ps->get_diameter(); // OUCH !!! Compilation error
कैसे करें डाउनकास्ट?
यदि आप यह सुनिश्चित करना जानते हैं कि ps
एक वृत्त की ओर static_cast
जिसे आप static_cast
लिए चुन सकते हैं:
std::cout << "Diameter: " << static_cast<Circle*>(ps)->get_diameter() << std::endl;
यह चाल चलेगा। लेकिन यह बहुत ही जोखिम भरा है: यदि ps
एक Circle
के अलावा किसी और चीज से प्रकट होता है तो आपके कोड का व्यवहार अपरिभाषित हो जाएगा।
इसलिए रूसी रूले खेलने के बजाय, आपको सुरक्षित रूप से एक dynamic_cast
उपयोग करना चाहिए। यह विशेष रूप से बहुरूपी वर्गों के लिए है:
int main() {
Circle circle(Point(0.0, 0.0), 10);
Shape &shape = circle;
std::cout << "The shape has a surface of " << shape.get_surface() << std::endl;
//shape.get_diameter(); // OUCH !!! Compilation error
Circle *pc = dynamic_cast<Circle*>(&shape); // will be nullptr if ps wasn't a circle
if (pc)
std::cout << "The shape is a circle of diameter " << pc->get_diameter() << std::endl;
else
std::cout << "The shape isn't a circle !" << std::endl;
}
ध्यान दें कि dynamic_cast
उस वर्ग पर संभव नहीं है जो बहुरूपी नहीं है। आपको कक्षा में कम से कम एक वर्चुअल फ़ंक्शन या उसके माता-पिता को इसका उपयोग करने में सक्षम होने की आवश्यकता होगी।
बहुरूपता और विनाशकारी
यदि किसी वर्ग का उद्देश्य बहुरूपिए से उपयोग किया जाना है, तो व्युत्पन्न उदाहरणों को आधार बिंदुओं / संदर्भों के रूप में संग्रहीत किया जाता है, इसका आधार वर्ग 'विध्वंसक या तो virtual
या protected
होना चाहिए। पूर्व मामले में, यह vtable
को जांचने के लिए ऑब्जेक्ट विनाश का कारण बनेगा, स्वचालित रूप से डायनेमिक प्रकार के आधार पर सही डिस्ट्रक्टर को vtable
। बाद के मामले में, बेस क्लास पॉइंटर / संदर्भ के माध्यम से ऑब्जेक्ट को नष्ट करना अक्षम है, और ऑब्जेक्ट को केवल तभी हटाया जा सकता है जब स्पष्ट रूप से उसके वास्तविक प्रकार के रूप में माना जाता है।
struct VirtualDestructor {
virtual ~VirtualDestructor() = default;
};
struct VirtualDerived : VirtualDestructor {};
struct ProtectedDestructor {
protected:
~ProtectedDestructor() = default;
};
struct ProtectedDerived : ProtectedDestructor {
~ProtectedDerived() = default;
};
// ...
VirtualDestructor* vd = new VirtualDerived;
delete vd; // Looks up VirtualDestructor::~VirtualDestructor() in vtable, sees it's
// VirtualDerived::~VirtualDerived(), calls that.
ProtectedDestructor* pd = new ProtectedDerived;
delete pd; // Error: ProtectedDestructor::~ProtectedDestructor() is protected.
delete static_cast<ProtectedDerived*>(pd); // Good.
ये दोनों प्रथाएं इस बात की गारंटी देती हैं कि व्युत्पन्न वर्ग 'विध्वंसक को हमेशा व्युत्पन्न वर्ग उदाहरणों पर बुलाया जाएगा, जिससे स्मृति रिसाव को रोका जा सकेगा।