वस्तु-उन्मुख प्रोग्रामिंग व C++

यह अध्याय NET के दो सन्निकट उप-प्रकरणों को साथ ढकता है: वस्तु-उन्मुखता की भाषा-स्वतंत्र संकल्पनाएँ (क्लास, वस्तु, दृष्टांतीकरण, वंशानुक्रम, संचयन, अमूर्त क्लास, बहुरूपता), तथा C++ को उस भाषा के रूप में जिसने इन्हें तंत्र-प्रोग्रामिंग में मुख्यधारा बनाया — इसके अपने टोकन, संकारक, नियंत्रण-प्रवाह, प्राचल-पासिंग, आभासी फलन, कंस्ट्रक्टर/डिस्ट्रक्टर, संकारक-अधिभारण, टेम्पलेट, अपवाद-प्रबंधन व स्ट्रीम। किसी का भी GATE CS समतुल्य नहीं है।

क्लास, वस्तु, वंशानुक्रम, संचयन व बहुरूपता

मूल OOP शब्दावली
शब्दअर्थ
क्लास / वस्तुक्लास एक ब्लूप्रिंट है (क्षेत्र + विधियाँ); वस्तु उसका एक दृष्टांतीकृत उदाहरण है, अपनी क्षेत्रों की अपनी प्रति सहित।
संचयनआँकड़े को उस पर संक्रिया करने वाली विधियों सहित बाँधना, तथा आंतरिक निरूपण को सार्वजनिक अंतरापृष्ठ (अभिगम-विनिर्देशक public/private/protected) के पीछे छिपाना।
वंशानुक्रमव्युत्पन्न क्लास आधार क्लास के सदस्य प्राप्त करता है, जोड़ते या अध्यारोहित करते हुए — IS-A संबंध प्रतिरूपित करता है (Dog IS-A Animal)।
बहुरूपताएक अंतरापृष्ठ, अनेक कार्यान्वयन। संकलन-समय (फलन/संकारक अधिभारण — आर्गुमेंट-प्रकारों से हल) बनाम रन-समय (आभासी फलन — वस्तु के वास्तविक प्रकार से हल)।
अमूर्त क्लासकम-से-कम एक शुद्ध आभासी फलन वाला क्लास, अतः इसे सीधे दृष्टांतीकृत नहीं किया जा सकता — यह केवल व्युत्पन्न किए जाने व व्युत्पन्न क्लासों को उस अंतरापृष्ठ को कार्यान्वित करने हेतु बाध्य करने के लिए विद्यमान है।
🎯 बहुरूपता को आधार-क्लास संकेतक/संदर्भ की आवश्यकता क्यों है
रन-समय बहुरूपता केवल किसी व्युत्पन्न वस्तु के आधार-क्लास संकेतक या संदर्भ से होकर चलती है — संकेतक से होकर आभासी विधि को बुलाना vtable से होकर वस्तु के वास्तविक (गतिक) प्रकार का कार्यान्वयन खोजता है, जबकि सादे (गैर-संकेतक, गैर-संदर्भ) आधार-क्लास वस्तु से होकर बुलाने पर व्युत्पन्न भाग कट जाता (स्लाइस) और सदा आधार-संस्करण ही बुलाया जाता। यह स्लाइसिंग व्यवहार इस उप-प्रकरण का मानक जाल है।

C++ टोकन, संकारक, प्राचल-पासिंग व आभासी फलन

  • C++ C के कॉल-बाय-वैल्यू में संदर्भ प्राचल (int &x) जोड़ता है: संदर्भ आमंत्रक के चर का उपनाम है, अतः void inc(int &x) { x++; } आमंत्रक के चर को सीधे संशोधित करता है — सादे C के विपरीत, स्पष्ट संकेतक/डी-रेफ़रेंस की आवश्यकता नहीं।
  • आभासी फलन: आधार-क्लास विधि को virtual घोषित करना आधार-संकेतक/संदर्भ से होकर कॉलों को रन-समय पर सबसे अधिक व्युत्पन्न अध्यारोहण पर प्रेषित करता है; शुद्ध आभासी फलन (= 0) का कोई आधार-कार्यान्वयन ही नहीं होता और वह क्लास को अमूर्त बनाता है।
  • कंस्ट्रक्टर व डिस्ट्रक्टर: कंस्ट्रक्टर वस्तु-सर्जन पर स्वतः चलता है (सदस्य आरंभीकृत करते हुए, प्रायः दक्षता हेतु व संदर्भ व const क्षेत्र जैसे एक-बार सेट होने वाले सदस्यों हेतु आरंभीकरण-सूची द्वारा); डिस्ट्रक्टर विनाश पर स्वतः चलता है (संसाधन मुक्त करते हुए) — किसी भी आधार क्लास पर, जिसे आधार-संकेतक से होकर मिटाया जाना है, आभासी डिस्ट्रक्टर आवश्यक है, अन्यथा व्युत्पन्न भाग का डिस्ट्रक्टर छूट जाता है, उसके संसाधन रिसते हुए।

अधिभारण, टेम्पलेट, अपवाद-प्रबंधन व स्ट्रीम

  • संकारक अधिभारण संकारकों (+, <<, ==) को उपयोगकर्ता-परिभाषित प्रकारों हेतु नए अर्थ देता है — यह संकारक की पूर्वता या साहचर्यता कभी नहीं बदलता, केवल यह कि दिए प्रकार-संयोजन हेतु कौन कोड चलता है।
  • टेम्पलेट संकलन-समय सामान्यीकृत प्रोग्रामिंग देते हैं: फलन या क्लास टेम्पलेट को संकलक हर उस ठोस प्रकार हेतु दृष्टांतीकृत करता है जिसके साथ वह वस्तुतः प्रयुक्त होता है, बिना किसी रन-समय प्रकार-प्रेषण भार के प्रकार-विशिष्ट कोड उत्पन्न करते हुए — रन-समय बहुरूपता से भिन्न, जो प्रति कॉल vtable-खोज लागत चुकाती है।
  • अपवाद-प्रबंधन: try/catch/throw त्रुटि-संसूचन कोड को त्रुटि-प्रबंधन कोड से पृथक करता है; try ब्लॉक के भीतर फेंका अपवाद स्टैक को अनवाइंड करता है (मार्ग में स्थानीय वस्तुओं के डिस्ट्रक्टर चलाते हुए) जब तक कोई मिलान करता catch हैंडलर न मिले, या यदि कोई न मिले तो प्रोग्राम समाप्त हो जाता है।
  • स्ट्रीम व फ़ाइल: cin/cout/cerr मानक-स्ट्रीम वस्तुएँ हैं जो >>/<< अधिभारित करती हैं; ifstream/ofstream उसी स्ट्रीम-अमूर्तन को फ़ाइलों तक विस्तारित करते हैं, अतः फ़ाइल I/O कंसोल I/O जैसा ही >>/<< वाक्य-रचना प्रयोग करता है।

बहु-फ़ाइल प्रोग्राम

बड़ा C++ प्रोग्राम एक हैडर फ़ाइल (.h, केवल घोषणाएँ — क्लास परिभाषाएँ, फलन प्रोटोटाइप) में विभाजित होता है जो जहाँ भी आवश्यक हो सम्मिलित की जाती है, तथा एक या अधिक स्रोत फ़ाइलों (.cpp, वास्तविक परिभाषाएँ) में, जो पृथक रूप से ऑब्जेक्ट फ़ाइलों में संकलित होकर लिंकर द्वारा संयोजित होती हैं। हैडर-गार्ड (#ifndef/#define/#endif, या #pragma once) उसी हैडर की घोषणाओं को दो बार प्रक्रमित होने से रोकते हैं यदि वह एक अनुवाद-इकाई में एक से अधिक बार (प्रत्यक्ष या संक्रमणीय रूप से) सम्मिलित हो — इनके बिना, कोई क्लास या फलन संकलक को 'पुनर्परिभाषित' प्रतीत होगा।

मुख्य बिंदु

  • क्लास ब्लूप्रिंट है, वस्तु उदाहरण; संचयन निरूपण को अंतरापृष्ठ के पीछे छिपाता है; वंशानुक्रम IS-A प्रतिरूपित करता है; बहुरूपता एक अंतरापृष्ठ अनेक कार्यान्वयनों सहित है, संकलन-समय (अधिभारण) या रन-समय (आभासी फलन)।
  • रन-समय बहुरूपता केवल आधार-क्लास संकेतक/संदर्भ से होकर चलती है; सादी आधार-वस्तु से होकर बुलाने पर व्युत्पन्न भाग कट जाता है।
  • आधार-संकेतक से होकर मिटाए जाने वाले आधार-क्लास को आभासी डिस्ट्रक्टर चाहिए, अन्यथा व्युत्पन्न भाग की सफ़ाई छूट जाती है।
  • टेम्पलेट संकलन-समय सामान्यीकरण हैं (कोई रन-समय प्रेषण-लागत नहीं); आभासी फलन रन-समय बहुरूपता हैं (प्रति कॉल vtable-खोज लागत) — भिन्न आवश्यकताओं हेतु भिन्न क्रियाविधियाँ।
  • हैडर-गार्ड किसी हैडर की घोषणाओं को एक अनुवाद-इकाई में दो बार प्रक्रमित होने से रोकते हैं।

अभ्यास प्रश्न (8)

उत्तर खोलने से पहले प्रत्येक प्रश्न हल करें। हर व्याख्या सही विकल्प के साथ लुभावना गलत विकल्प भी बताती है, क्योंकि अंक वहीं जाते हैं।

  1. C++ में रन-समय बहुरूपता तब चलती है जब किसी आभासी विधि को बुलाया जाए:

    1. किसी व्युत्पन्न वस्तु से बँधे आधार-क्लास संकेतक या संदर्भ से होकर
    2. सादी (गैर-संकेतक, गैर-संदर्भ) आधार-क्लास वस्तु से होकर
    3. टेम्पलेट दृष्टांतीकरण से होकर
    4. प्रीप्रोसेसर मैक्रो से होकर
    उत्तर देखें

    उत्तर: A — किसी व्युत्पन्न वस्तु से बँधे आधार-क्लास संकेतक या संदर्भ से होकर

    आधार-संकेतक/संदर्भ से होकर बुलाना vtable से वस्तु का वास्तविक गतिक प्रकार खोजता है; सादी आधार-वस्तु से होकर बुलाने पर इसके बजाय व्युत्पन्न भाग कट जाता है और सदा आधार-संस्करण ही बुलाया जाता है।
  2. कम-से-कम एक शुद्ध आभासी फलन रखने वाला क्लास है:

    1. अमूर्त, और इसे सीधे दृष्टांतीकृत नहीं किया जा सकता
    2. इसके किसी व्युत्पन्न क्लास न होने की गारंटी
    3. स्वतः हर अन्य क्लास का आधार
    4. व्यवहार में टेम्पलेट क्लास के समान
    उत्तर देखें

    उत्तर: A — अमूर्त, और इसे सीधे दृष्टांतीकृत नहीं किया जा सकता

    शुद्ध आभासी फलन (= 0 सहित घोषित) का उस क्लास में कोई कार्यान्वयन नहीं होता, अतः अकेले उसकी वस्तु अपूर्ण होगी — ऐसा क्लास केवल व्युत्पन्न किए जाने हेतु विद्यमान है, और व्युत्पन्न क्लासों को स्वयं दृष्टांतीकरण-योग्य बनने हेतु शुद्ध आभासी फलन कार्यान्वित करना ही होगा।
  3. C++ में संकलन-समय बहुरूपता प्राप्त होती है:

    1. फलन/संकारक अधिभारण से, संकलन-समय पर आर्गुमेंट-प्रकारों से हल
    2. आभासी फलनों से
    3. अपवाद-प्रबंधन से
    4. गतिक स्मृति आवंटन से
    उत्तर देखें

    उत्तर: A — फलन/संकारक अधिभारण से, संकलन-समय पर आर्गुमेंट-प्रकारों से हल

    अधिभार-निर्धारण पूर्णतः संकलन-समय पर आर्गुमेंटों की संख्या व प्रकारों के आधार पर होता है; आभासी फलन इसके विपरीत रन-समय पर वस्तु के वास्तविक प्रकार के आधार पर हल होते हैं — रन-समय बहुरूपता क्रियाविधि।
  4. जिस आधार क्लास की वस्तुएँ आधार-क्लास संकेतकों से होकर मिटाई जाती हैं, उसे यह होना चाहिए:

    1. आभासी डिस्ट्रक्टर
    2. कोई डिस्ट्रक्टर ही नहीं
    3. टेम्पलेट डिस्ट्रक्टर
    4. प्राइवेट कंस्ट्रक्टर
    उत्तर देखें

    उत्तर: A — आभासी डिस्ट्रक्टर

    आभासी डिस्ट्रक्टर के बिना, आधार-संकेतक से होकर व्युत्पन्न वस्तु मिटाने पर केवल आधार क्लास का डिस्ट्रक्टर चलता है, व्युत्पन्न भाग की सफ़ाई छूट जाती है और उसके रखे संसाधन रिसते हैं।
  5. C++ फलन टेम्पलेट, आभासी फलन की तुलना में, बहुरूपता प्राप्त करता है:

    1. संकलन-समय पर, बिना किसी रन-समय प्रेषण-भार के
    2. केवल रन-समय पर, प्रति कॉल vtable-खोज सहित
    3. किसी पर नहीं — टेम्पलेट बहुरूपता से असंबद्ध हैं
    4. केवल अपवाद-प्रबंधन के साथ संयुक्त होने पर
    उत्तर देखें

    उत्तर: A — संकलन-समय पर, बिना किसी रन-समय प्रेषण-भार के

    संकलक टेम्पलेट का एक पृथक, प्रकार-विशिष्ट संस्करण हर उस प्रकार हेतु दृष्टांतीकृत करता है जिसके साथ वह प्रयुक्त होता है, अतः सही कोड पूर्णतः संकलन-समय पर चुना जाता है, बिना किसी रन-समय प्रकार-प्रेषण लागत के, आभासी फलनों के विपरीत।
  6. जब try ब्लॉक के भीतर कोई अपवाद फेंका जाए और वर्तमान फलन में कोई मिलान करता catch हैंडलर विद्यमान न हो:

    1. स्टैक आगे अनवाइंड होता है, स्थानीय डिस्ट्रक्टर चलाते हुए, कॉल-शृंखला में ऊपर मिलान करते हैंडलर की खोज में
    2. अपवाद चुपचाप नज़रअंदाज़ कर दिया जाता है
    3. प्रोग्राम main से पुनः आरंभ होता है
    4. यह संकलन-त्रुटि बन जाता है
    उत्तर देखें

    उत्तर: A — स्टैक आगे अनवाइंड होता है, स्थानीय डिस्ट्रक्टर चलाते हुए, कॉल-शृंखला में ऊपर मिलान करते हैंडलर की खोज में

    स्टैक-अनवाइंडिंग कॉल-शृंखला में ऊपर जारी रहती है, मार्ग में स्थानीय वस्तुओं को नष्ट करते हुए, जब तक शृंखला में कहीं मिलान करता हैंडलर न मिले, या यदि कहीं भी न हो तो प्रोग्राम समाप्त (सामान्यतः std::terminate द्वारा) हो जाता है।
  7. सादे C संकेतक-प्राचलों से भिन्न, C++ संदर्भ-प्राचलों के विषय में निम्नलिखित में कौन सत्य हैं? (एक से अधिक विकल्प सही हो सकते हैं।)

    1. संदर्भ आमंत्रक के चर का उपनाम है और फलन के भीतर स्पष्ट डी-रेफ़रेंस संकारक की आवश्यकता नहीं
    2. संदर्भ-प्राचल संशोधित करना आमंत्रक के मूल चर को संशोधित करता है
    3. प्रयोग से पहले संदर्भ को ठीक संकेतक की भाँति null-जाँचा जाना चाहिए
    4. आरंभीकरण के बाद संदर्भ को भिन्न चर की ओर इंगित करने हेतु पुनः स्थापित किया जा सकता है
    उत्तर देखें

    उत्तर: A — संदर्भ आमंत्रक के चर का उपनाम है और फलन के भीतर स्पष्ट डी-रेफ़रेंस संकारक की आवश्यकता नहीं; B — संदर्भ-प्राचल संशोधित करना आमंत्रक के मूल चर को संशोधित करता है

    संदर्भ आरंभीकरण पर एक बार बँधता है और उसे पुनः स्थापित नहीं किया जा सकता, और (संकेतक के विपरीत) सुसंरचित संदर्भ null नहीं हो सकता, अतः रूढ़िगत रूप से किसी null-जाँच की आवश्यकता नहीं — ये ठीक वे दो गुण हैं जो इसे पता-धारक चर के बजाय उपनाम बनाते हैं।
  8. किसी हैडर फ़ाइल की विषय-वस्तु को एक अनुवाद-इकाई में एक से अधिक बार प्रक्रमित होने से रोकने हेतु प्रयुक्त प्रीप्रोसेसर क्रियाविधि (तीन निर्देश, या एक अ-मानक-पर-व्यापक-रूप से समर्थित प्रैग्मा) का नाम बताइए।

    संख्यात्मक उत्तर — मान टाइप करें।

    उत्तर देखें

    उत्तर: header guard

    #ifndef/#define/#endif हैडर-गार्ड (या अधिकांश संकलकों द्वारा समर्थित #pragma once लघु-मार्ग) किसी क्लास या फलन को 'पुनर्परिभाषित' प्रतीत होने से रोकता है जब वही हैडर, प्रत्यक्ष या संक्रमणीय रूप से, एक से अधिक बार सम्मिलित हो।