C ++ में नेस्टेड क्लास का उपयोग क्यों करेगा?


188

क्या कोई मुझे समझने और नेस्टेड कक्षाओं का उपयोग करने के लिए कुछ अच्छे संसाधनों की ओर संकेत कर सकता है? मेरे पास कुछ सामग्री है जैसे प्रोग्रामिंग सिद्धांत और इस तरह के आईबीएम नॉलेज सेंटर - नेस्टेड क्लासेस

लेकिन मुझे अभी भी उनके उद्देश्य को समझने में परेशानी हो रही है। क्या कोई मेरी सहायता कर सकता है?


15
सी ++ में नेस्टेड कक्षाओं के लिए मेरी सलाह बस नेस्टेड कक्षाओं का उपयोग नहीं करना है।
बिली ओनेल

7
वे बिल्कुल नियमित कक्षाओं की तरह हैं ... नेस्टेड को छोड़कर। जब कक्षा का आंतरिक कार्यान्वयन इतना जटिल हो कि उनका उपयोग कई छोटे वर्गों द्वारा आसानी से किया जा सके।
मेगर

12
@ बिली: क्यों? मेरे लिए अधिक व्यापक लगता है।
जॉन डिब्लिंग

30
मैंने अभी भी एक तर्क नहीं देखा है कि नेस्टेड क्लास उनके स्वभाव से खराब क्यों हैं।
जॉन डिब्लिंग

7
@ 7vies: 1. क्योंकि यह केवल आवश्यक नहीं है - आप बाहरी रूप से परिभाषित कक्षाओं के साथ भी ऐसा कर सकते हैं, जो किसी भी दिए गए चर के दायरे को कम करता है, जो एक अच्छी बात है। 2. क्योंकि आप सब कुछ कर सकते हैं नेस्टेड क्लास के साथ कर सकते हैं typedef। 3. क्योंकि वे एक ऐसे वातावरण में इंडेंटेशन का एक अतिरिक्त स्तर जोड़ते हैं जहां लंबी लाइनों से बचना पहले से ही मुश्किल है। 4. क्योंकि आप एक ही classघोषणा में दो वैचारिक रूप से अलग-अलग वस्तुओं की घोषणा कर रहे हैं , आदि
बिली ऑनेल

जवाबों:


229

नेस्टेड कक्षाएं कार्यान्वयन विवरण को छिपाने के लिए शांत हैं।

सूची:

class List
{
    public:
        List(): head(nullptr), tail(nullptr) {}
    private:
        class Node
        {
              public:
                  int   data;
                  Node* next;
                  Node* prev;
        };
    private:
        Node*     head;
        Node*     tail;
};

यहां मैं नोड को उजागर नहीं करना चाहता हूं क्योंकि अन्य लोग वर्ग का उपयोग करने का निर्णय ले सकते हैं और जो मुझे मेरी कक्षा को अपडेट करने से रोक देगा क्योंकि कुछ भी सार्वजनिक एपीआई का हिस्सा है और इसे हमेशा बनाए रखा जाना चाहिए । कक्षा को निजी बनाकर, मैं न केवल उस कार्यान्वयन को छिपाता हूं जो मैं यह कह रहा हूं कि यह मेरा है और मैं इसे किसी भी समय बदल सकता हूं, इसलिए आप इसका उपयोग नहीं कर सकते।

देखो std::listया std::mapवे सभी में छिपे हुए वर्ग हैं (या क्या वे?)। मुद्दा यह है कि वे कर सकते हैं या नहीं, लेकिन क्योंकि कार्यान्वयन निजी है और STL के छिपे हुए बिल्डरों ने कोड को अपडेट किए बिना यह प्रभावित करने में सक्षम थे कि आपने कोड का उपयोग कैसे किया, या एसटीएल के आसपास बहुत सारे पुराने सामान रखना छोड़ दिया क्योंकि उन्हें ज़रूरत है कुछ मूर्खों के साथ पीछे की संगतता बनाए रखने के लिए जिन्होंने फैसला किया कि वे अंदर छिपे हुए नोड वर्ग का उपयोग करना चाहते थे list


9
यदि आप ऐसा कर रहे हैं तो Nodeहेडर फ़ाइल में बिल्कुल भी उजागर नहीं होना चाहिए।
बिली ओनेल

6
@ बिली ओनली: क्या होगा अगर मैं एसटीएल या बूस्ट की तरह एक हेडर फ़ाइल कार्यान्वयन कर रहा हूं।
मार्टिन यॉर्क

5
@ बिली ओनली: नहीं। अच्छी बात है राय नहीं। इसे किसी नामस्थान में रखने से यह उपयोग से बचाता नहीं है। यह अब सार्वजनिक एपीआई का हिस्सा है जिसे हमेशा के लिए बनाए रखने की आवश्यकता है।
मार्टिन यॉर्क

21
@ बिली ओनली: यह इसे आकस्मिक उपयोग से बचाता है। यह इस तथ्य का भी दस्तावेज है कि यह निजी है और इसका उपयोग नहीं किया जाना चाहिए (तब तक इसका उपयोग नहीं किया जा सकता जब तक कि आप कुछ बेवकूफ न करें)। इस प्रकार आपको इसका समर्थन करने की आवश्यकता नहीं है। इसे नाम स्थान में रखने से यह सार्वजनिक API का हिस्सा बन जाता है (कुछ जिसे आप इस वार्तालाप में गायब रखते हैं। सार्वजनिक API का अर्थ है कि आपको समर्थन करने की आवश्यकता है)।
मार्टिन यॉर्क

10
@ बिली ओनली: नेस्टेड नेमस्पेस पर नेस्टेड क्लास का कुछ फायदा है: आप किसी नेमस्पेस के इंस्टेंस नहीं बना सकते हैं, लेकिन आप एक क्लास के इंस्टेंस बना सकते हैं। के रूप में detailसम्मेलन: इसके बजाय ऐसे सम्मेलनों के आधार पर एक की जरूरत को ध्यान में अपने आप को बनाए रखने के लिए, यह संकलक जो आप के लिए उन पर नज़र रखता है पर निर्भर रहना बेहतर है।
सासक

142

नेस्टेड कक्षाएं नियमित कक्षाओं की तरह हैं, लेकिन:

  • उनके पास अतिरिक्त पहुंच प्रतिबंध है (जैसा कि एक वर्ग परिभाषा के अंदर सभी परिभाषाएं हैं),
  • वे दिए गए नामस्थान , जैसे वैश्विक नामस्थान, को प्रदूषित नहीं करते हैं । यदि आपको लगता है कि कक्षा बी कक्षा ए से बहुत गहराई से जुड़ा हुआ है, लेकिन ए और बी की वस्तुएं आवश्यक रूप से संबंधित नहीं हैं, तो आप चाहते हैं कि वर्ग बी ए वर्ग को स्कैन करने के माध्यम से ही सुलभ हो (इसे ए के रूप में संदर्भित किया जाएगा। ::कक्षा)।

कुछ उदाहरण:

सार्वजनिक रूप से घोंसला बनाने वाला वर्ग इसे प्रासंगिक वर्ग के दायरे में रखता है


मान लें कि आपके पास एक वर्ग है SomeSpecificCollectionजो कक्षा की वस्तुओं को एकत्रित करेगा Element। आप या तो कर सकते हैं:

  1. दो वर्गों की घोषणा करें: SomeSpecificCollectionऔर Element- बुरा, क्योंकि नाम "तत्व" एक संभावित नाम संघर्ष का कारण बनने के लिए सामान्य है

  2. एक नेमस्पेस शुरू करें someSpecificCollectionऔर कक्षाएं घोषित करें someSpecificCollection::Collectionऔर someSpecificCollection::Element। नाम संघर्ष का कोई जोखिम नहीं है, लेकिन क्या यह कोई और क्रिया प्राप्त कर सकता है?

  3. दो वैश्विक वर्गों की घोषणा करें SomeSpecificCollectionऔर SomeSpecificCollectionElement- जिनमें मामूली कमियां हैं, लेकिन संभवतः ठीक है।

  4. वैश्विक वर्ग SomeSpecificCollectionऔर वर्ग Elementको इसका नेस्टेड वर्ग घोषित करें । फिर:

    • आप किसी भी नाम की गड़बड़ी का जोखिम नहीं उठाते क्योंकि तत्व वैश्विक नामस्थान में नहीं है,
    • के कार्यान्वयन में SomeSpecificCollectionआप सिर्फ Element, और हर जगह के रूप में संदर्भित करता है SomeSpecificCollection::Element- जो दिखता है + - के रूप में एक ही 3., लेकिन अधिक स्पष्ट
    • यह सरल हो जाता है कि यह "एक विशिष्ट संग्रह का एक तत्व" है, न कि "किसी संग्रह का विशिष्ट तत्व"
    • यह दिखाई दे रहा है कि SomeSpecificCollectionएक वर्ग भी है।

मेरी राय में, अंतिम संस्करण निश्चित रूप से सबसे सहज और इसलिए सबसे अच्छा डिजाइन है।

मुझे तनाव दें - दो वैश्विक वर्गों को अधिक क्रिया नाम देने से कोई बड़ा अंतर नहीं है। यह सिर्फ एक छोटा सा विवरण है, लेकिन यह कोड को अधिक स्पष्ट बनाता है।

एक वर्ग दायरे के अंदर एक और गुंजाइश का परिचय


यह विशेष रूप से टाइपडिफ्स या एनम को पेश करने के लिए उपयोगी है। मैं यहाँ एक कोड उदाहरण पोस्ट करूँगा:

class Product {
public:
    enum ProductType {
        FANCY, AWESOME, USEFUL
    };
    enum ProductBoxType {
        BOX, BAG, CRATE
    };
    Product(ProductType t, ProductBoxType b, String name);

    // the rest of the class: fields, methods
};

एक तो फोन करेगा:

Product p(Product::FANCY, Product::BOX);

लेकिन जब कोड पूरा होने के प्रस्तावों को देखते हैं Product::, तो एक बार में सभी संभावित एनम मान (BOX, FANCY, CRATE) सूचीबद्ध हो जाएंगे और यहां एक गलती करना आसान है (C ++ 0x के दृढ़ता से टाइप किए गए एनम की तरह हल करें, लेकिन कभी भी बुरा मत मानना )।

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

class Product {
public:
    struct ProductType {
        enum Enum { FANCY, AWESOME, USEFUL };
    };
    struct ProductBoxType {
        enum Enum { BOX, BAG, CRATE };
    };
    Product(ProductType::Enum t, ProductBoxType::Enum b, String name);

    // the rest of the class: fields, methods
};

तब कॉल ऐसा लगता है:

Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);

फिर Product::ProductType::एक आईडीई में टाइप करके , किसी को सुझाए गए वांछित दायरे से केवल एनम मिलेगा। इससे गलती करने का जोखिम भी कम हो जाता है।

बेशक यह छोटी कक्षाओं के लिए आवश्यक नहीं हो सकता है, लेकिन अगर किसी के पास बहुत सारे एनम हैं, तो यह क्लाइंट प्रोग्रामर के लिए चीजों को आसान बनाता है।

उसी तरह, यदि आप कभी भी जरूरत पड़ते हैं, तो आप खाके में टाइपफेड का एक बड़ा समूह "व्यवस्थित" कर सकते हैं। यह कभी-कभी एक उपयोगी पैटर्न है।

PIMPL मुहावरा


PIMPL (Pointer से IMPLementation के लिए छोटा) हेडर से एक वर्ग के कार्यान्वयन विवरण को हटाने के लिए उपयोगी एक मुहावरा है। जब भी हेडर के "कार्यान्वयन" भाग में परिवर्तन होता है, तब यह वर्ग 'हेडर के आधार पर वर्गों को फिर से जोड़ने की आवश्यकता को कम करता है।

यह आमतौर पर एक नेस्टेड वर्ग का उपयोग करके लागू किया जाता है:

Xh:

class X {
public:
    X();
    virtual ~X();
    void publicInterface();
    void publicInterface2();
private:
    struct Impl;
    std::unique_ptr<Impl> impl;
}

X.cpp:

#include "X.h"
#include <windows.h>

struct X::Impl {
    HWND hWnd; // this field is a part of the class, but no need to include windows.h in header
    // all private fields, methods go here

    void privateMethod(HWND wnd);
    void privateMethod();
};

X::X() : impl(new Impl()) {
    // ...
}

// and the rest of definitions go here

यह विशेष रूप से उपयोगी है अगर पूर्ण वर्ग की परिभाषा को कुछ बाहरी पुस्तकालय से प्रकारों की परिभाषा की आवश्यकता होती है जिसमें एक भारी या सिर्फ बदसूरत हेडर फ़ाइल होती है (WinAPI लें)। यदि आप PIMPL का उपयोग करते हैं, तो आप केवल किसी भी WinAPI- विशिष्ट कार्यक्षमता को संलग्न कर सकते हैं .cppऔर इसे कभी भी इसमें शामिल नहीं कर सकते .h


3
struct Impl; std::auto_ptr<Impl> impl; इस त्रुटि को हर्ब सटर ने लोकप्रिय बनाया। अपूर्ण प्रकारों पर auto_ptr का उपयोग न करें, या गलत कोड उत्पन्न होने से बचने के लिए कम से कम सावधानी बरतें।
जीन बुशुयेव 20

2
@ बिली ओनली: जहां तक ​​मैं जानता हूं कि आप auto_ptrअधिकांश कार्यान्वयन में अपूर्ण प्रकार की घोषणा कर सकते हैं, लेकिन तकनीकी रूप से यह C ++ 0x (जैसे unique_ptr) में कुछ टेम्पलेट्स के विपरीत यूबी है जहां यह स्पष्ट किया गया है कि टेम्पलेट पैरामीटर हो सकता है एक अपूर्ण प्रकार और जहां वास्तव में प्रकार पूरा होना चाहिए। (उदा। उपयोग ~unique_ptr)
सीबी बेली

2
@ बिली ओनली: C ++ 03 17.4.6.3 में [lib.res.on.functions] कहते हैं "विशेष रूप से, निम्नलिखित मामलों में प्रभाव अपरिभाषित होते हैं: [...] यदि एक अपूर्ण प्रकार का उपयोग टेम्पलेट तर्क के रूप में किया जाता है। जब एक टेम्पलेट घटक को तत्काल करना। हालांकि C ++ 0x में यह कहा गया है "यदि एक घटक घटक को इंस्टेंट करते समय एक अपूर्ण प्रकार को टेम्पलेट तर्क के रूप में उपयोग किया जाता है, जब तक कि उस घटक के लिए विशेष रूप से अनुमति नहीं दी जाती है।" और बाद में (उदाहरण के लिए): "टेम्पलेट पैरामीटर Tकी unique_ptrएक अधूरी प्रकार हो सकता है।"
सीबी बेली

1
@ मील्सटाउट वैसे भी बहुत सामान्य है। निर्भर करता है कि क्लाइंट कोड को इनहेरिट करने की अनुमति है या नहीं। नियम: यदि आप निश्चित हैं कि आप बेस क्लास पॉइंटर से नहीं हटेंगे, तो वर्चुअल डोर पूरी तरह से बेमानी है।
कोस

2
@IsaacPascual aww, मुझे अपडेट करना चाहिए कि अब हमारे पास है enum class
कोस

21

मैं नेस्टेड कक्षाओं का उपयोग नहीं करता हूं, लेकिन मैं उनका उपयोग अभी और फिर करता हूं। विशेष रूप से जब मैं किसी प्रकार के डेटा प्रकार को परिभाषित करता हूं, और मैं तब उस डेटा प्रकार के लिए डिज़ाइन किए गए STL फ़ंक्टर को परिभाषित करना चाहता हूं।

उदाहरण के लिए, एक सामान्य Fieldवर्ग पर विचार करें जिसमें एक आईडी नंबर, एक प्रकार का कोड और एक फ़ील्ड नाम है। अगर मैं आईडी नंबर या नाम vectorसे इनमें Fieldसे किसी एक को खोजना चाहता हूं , तो मैं ऐसा करने के लिए एक फ़नकार का निर्माण कर सकता हूं:

class Field
{
public:
  unsigned id_;
  string name_;
  unsigned type_;

  class match : public std::unary_function<bool, Field>
  {
  public:
    match(const string& name) : name_(name), has_name_(true) {};
    match(unsigned id) : id_(id), has_id_(true) {};
    bool operator()(const Field& rhs) const
    {
      bool ret = true;
      if( ret && has_id_ ) ret = id_ == rhs.id_;
      if( ret && has_name_ ) ret = name_ == rhs.name_;
      return ret;
    };
    private:
      unsigned id_;
      bool has_id_;
      string name_;
      bool has_name_;
  };
};

फिर कोड जिसे इन Fieldएस के लिए खोज करने की आवश्यकता है matchवह Fieldवर्ग के भीतर स्कूप्ड का उपयोग कर सकता है:

vector<Field>::const_iterator it = find_if(fields.begin(), fields.end(), Field::match("FieldName"));

महान उदाहरण और टिप्पणियों के लिए धन्यवाद, हालांकि मैं एसटीएल कार्यों के साथ काफी जागरूक नहीं हूं। मैं नोटिस करता हूं कि मैच में कंस्ट्रक्टर सार्वजनिक हैं। मुझे लगता है कि कंस्ट्रक्टरों को हमेशा सार्वजनिक नहीं होना चाहिए, इस मामले में इसे कक्षा के बाहर तत्काल नहीं किया जा सकता है।
चश्माधारी

1
@user: एसटीएल फन्ने के मामले में, कंस्ट्रक्टर को सार्वजनिक होने की आवश्यकता होती है।
जॉन डिब्लिंग

1
@ बिली: मुझे अभी भी कोई ठोस तर्क दिखाई नहीं दे रहा है कि नेस्टेड क्लास क्यों खराब हैं।
जॉन डिब्लिंग

@ जॉन: सभी कोडिंग शैली दिशानिर्देश एक राय के मामले में नीचे आते हैं। मैंने यहाँ कई टिप्पणियों में कई कारण सूचीबद्ध किए हैं, जिनमें से सभी (मेरी राय में) उचित हैं। कोई "तथ्यात्मक" तर्क नहीं है जो कोड को मान्य है और जब तक कोई अपरिभाषित व्यवहार नहीं करता है तब तक इसे बनाया जा सकता है। हालांकि, मुझे लगता है कि आपके द्वारा यहां रखा गया कोड उदाहरण एक बड़ा कारण बताता है कि मैं नेस्टेड क्लासेस से बचने का नाम क्यों देता हूं - जिसका नाम क्लैश है।
बिली ओनेल

1
बेशक मैक्रों को इनलाइन पसंद करने के तकनीकी कारण हैं !!
माइल्स राउत

14

एक नेस्टर्ड क्लास के साथ एक बिल्डर पैटर्न को लागू कर सकता है । विशेष रूप से सी ++ में, व्यक्तिगत रूप से मुझे यह शब्दार्थिक रूप से क्लीनर लगता है। उदाहरण के लिए:

class Product{
    public:
        class Builder;
}
class Product::Builder {
    // Builder Implementation
}

बजाय:

class Product {}
class ProductBuilder {}

यकीन है, यह काम करेगा अगर वहाँ केवल एक ही निर्माण है, लेकिन कई ठोस बिल्डरों की आवश्यकता होने पर बुरा हो जाएगा। एक को ध्यान से डिजाइन निर्णय लेना चाहिए :)
irsis
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.