C ++ में हतोत्साहित बेस-फॉर-ऑल-ऑब्जेक्ट्स क्यों है


76

स्ट्रॉस्ट्रप कहते हैं, "अपनी सभी कक्षाओं (ऑब्जेक्ट क्लास) के लिए तुरंत एक अद्वितीय आधार का आविष्कार न करें। आमतौर पर, आप इसके बिना कई / अधिकांश कक्षाओं के लिए बेहतर कर सकते हैं।" (C ++ प्रोग्रामिंग लैंग्वेज फोर्थ एडिशन, सेक 1.3.4)

बेस-क्लास-फॉर-एवरीथिंग आमतौर पर एक बुरा विचार क्यों है, और यह कब एक बनाने के लिए समझ में आता है?


16
क्योंकि C ++ जावा नहीं है ... और आपको इसे मजबूर करने की कोशिश नहीं करनी चाहिए।
एके_

10
स्टैक ओवरफ्लो पर पूछा गया: C ++ में कोई बेस क्लास क्यों नहीं है?

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

2
यह "आप इसे करने की जरूरत नहीं है" चुस्त के सिद्धांत है। जब तक आपने इसके लिए किसी विशेष आवश्यकता की पहचान नहीं की है, तब तक ऐसा न करें (जब तक आप ऐसा न करें)।
जूल

3
@AK_: आपकी टिप्पणी में एक "जैसा बेवकूफ" है।
डेडएमजी

जवाबों:


75

क्योंकि कार्यक्षमता के लिए उस वस्तु का क्या होगा? जावा में सभी बेस क्लास में एक toString, एक हैशकोड और समानता और एक मॉनिटर + कंडीशन वैरिएबल है।

  • डीबगिंग के लिए टॉग्रिंग ही उपयोगी है।

  • हैशकोड केवल तब उपयोगी होता है जब आप इसे हैश-आधारित संग्रह में संग्रहीत करना चाहते हैं (C ++ में वरीयता कंटेनर के लिए एक हैशिंग फ़ंक्शन को टेम्पलेट पैराम के रूप में पास करना है या std::unordered_*पूरी तरह से बचने के लिए और इसके बजाय उपयोग std::vectorऔर सादे अव्यवस्थित सूचियों के लिए)।

  • आधार वस्तु के बिना समानता को संकलन समय पर मदद की जा सकती है, यदि उनके पास एक ही प्रकार नहीं है तो वे समान नहीं हो सकते। C ++ में यह एक संकलन समय त्रुटि है।

  • मॉनिटर और कंडीशन वेरिएबल को केस के आधार पर किसी केस में स्पष्ट रूप से शामिल किया जाता है।

हालाँकि जब अधिक है कि इसे करने की आवश्यकता है तो उपयोग-मामला है।

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


7
आप शायद उल्लेख करना भूल गए हैं कि मुख्य कारण जावा में एक बेस क्लास है: जेनेरिक से पहले, संग्रह कक्षाओं को कार्य करने के लिए बेस क्लास की आवश्यकता थी। सब कुछ (आंतरिक भंडारण, पैरामीटर, रिटर्न मान) टाइप किया गया था Object
अलेक्सांद्र डबिन्स्की

1
@AleksandrDubinsky: और जेनेरिक ने केवल वाक्यात्मक चीनी को जोड़ा, वास्तव में किसी भी तरह से नहीं बल्कि पॉलिश को बदल दिया।
डिडुप्लिकेटर

4
मैं तर्क दूंगा, हैश कोड, समानता और मॉनिटर समर्थन जावा में डिजाइन की गलतियां भी हैं। किसने सोचा था कि सभी वस्तुओं को ताला बनाने के लिए यह एक अच्छा विचार है ?!
usr

1
हाँ, लेकिन कोई नहीं चाहता। जब आखिरी बार आपको किसी ऑब्जेक्ट को लॉक करने की आवश्यकता थी और ऐसा करने के लिए एक अलग लॉक ऑब्जेक्ट को तुरंत नहीं कर सकता था। यह बहुत दुर्लभ है और यह हर चीज पर बोझ डालता है। जावा के लोगों को थ्रेड सेफ्टी की बुरी समझ थी और सभी वस्तुओं द्वारा एक ताला होने के कारण और थ्रेड सेफ कलेक्शन के सबूतों के रूप में जिन्हें अब हटा दिया गया है। धागा सुरक्षा एक वैश्विक संपत्ति है, न कि प्रति-वस्तु।
यूआर

2
" हैशकोड केवल तभी उपयोगी होता है जब आप इसे हैश-आधारित संग्रह में संग्रहीत करना चाहते हैं (C ++ में वरीयता std :: वेक्टर और सादे अव्यवस्थित सूचियों के लिए है)। " असली प्रतिवाद _hashCodeएक अलग कंटेनर का उपयोग करने के लिए नहीं है, बल्कि इंगित करता है। यह कि C ++ का std::unordered_mapहैमिंग को टेम्प्लेट तर्क का उपयोग करना है, बजाय कार्यान्वयन प्रदान करने के लिए तत्व वर्ग की आवश्यकता के। यही है, C ++ में अन्य सभी अच्छे कंटेनरों और संसाधन-प्रबंधकों की तरह, यह गैर-घुसपैठ है; यह कार्य या डेटा के साथ सभी वस्तुओं को प्रदूषित नहीं करता है अगर किसी को बाद में किसी संदर्भ में उनकी आवश्यकता हो सकती है।
अंडरस्कोर_ड

100

क्योंकि सभी वस्तुओं द्वारा साझा किए गए कोई कार्य नहीं हैं। इस इंटरफ़ेस में कुछ भी नहीं है जो सभी वर्गों के लिए समझ में आता है।


10
उत्तर की सादगी के लिए +1, यह वास्तव में एकमात्र कारण है।
बीडब्ल्यूजी

7
जिस बड़े ढांचे में मेरा अनुभव है, वह सामान्य आधार वर्ग क्रमबद्धता और परावर्तन अवसंरचना प्रदान करता है जो कि <जो भी> संदर्भ में वांछित है। भावहीन। यह सिर्फ डेटा और मेटाडेटा के साथ एक साथ क्रॉफ़्ट के एक झुंड को क्रमबद्ध करने वाले लोगों के परिणामस्वरूप हुआ और कुशल होने के लिए डेटा प्रारूप को बहुत बड़ा और जटिल बना दिया।
dmckee

19
@ डमकी: मैं यह भी तर्क दूंगा कि क्रमबद्धता और प्रतिबिंब शायद ही सार्वभौमिक रूप से उपयोगी आवश्यकताएं हैं।
डेडएमजी

16
@DeadMG: "लेकिन क्या अगर आपको हर बार बचत करने की जरूरत है?"
डेवॉर्ड

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

25

जब भी आप उन वस्तुओं के लम्बे वंशानुगत पदानुक्रम का निर्माण करते हैं जिन्हें आप फ्रैजाइल बेस क्लास (विकिपीडिया) की समस्या में चलाने के लिए करते हैं ।

कई छोटे अलग (अलग, पृथक) वंशानुगत पदानुक्रम इस समस्या में चलने की संभावना को कम करते हैं।

आपकी सभी वस्तुओं को एक एकल मानवीय विरासत पदानुक्रम का हिस्सा बनाना व्यावहारिक रूप से गारंटी देता है कि आप इस समस्या में भाग लेने जा रहे हैं।


6
जब बेस क्लास (जावा में "java.lang.Object") में फ्रैगाइल बेस क्लास समस्या नहीं हो सकती है, तो अन्य विधियों को कॉल करने वाली कोई विधियाँ शामिल नहीं हैं।
मार्टिन रोसेनॉ

3
एक शक्तिशाली उपयोगी आधार वर्ग जो होगा!
माइक नाकिस

9
@MartinRosenau ... जैसे आप मास्टर बेस क्लास की आवश्यकता के बिना C ++ में कर सकते हैं!
gbjbaanb

5
@ Davor stupiddralo So C ++ में एक बुनियादी फ़ंक्शन ("ऑपरेटर <<" के लिए "डिबगप्रिंट" जैसी समझदारी के बजाय एक बेवकूफ नाम है), जबकि जावा में आपके द्वारा लिखे गए हर वर्ग के लिए आधार वर्ग की एक सनकी है, कोई अपवाद नहीं। मुझे लगता है कि मुझे C ++ का मस्सा अधिक पसंद है।
सेबेस्टियन रेडल

4
@ Davor functiondralo: फ़ंक्शन का नाम अप्रासंगिक है। छवि एक वाक्यविन्यास cout.print(x).print(0.5).print("Bye\n")- यह पर टिका नहीं है operator<<
एमएसल्टर्स

24

चूंकि:

  1. आप जो उपयोग नहीं करते हैं उसके लिए आपको भुगतान नहीं करना चाहिए।
  2. ये फ़ंक्शन एक संदर्भ-आधारित प्रकार की प्रणाली की तुलना में मूल्य-आधारित प्रकार प्रणाली में कम समझ में आते हैं।

किसी भी प्रकार का कार्यान्वयनvirtual समारोह एक आभासी-टेबल, जो प्रति-वस्तु अंतरिक्ष भूमि के ऊपर न आवश्यक है और न ही कई (सबसे?) स्थितियों में वांछित है कि आवश्यकता है प्रस्तुत करता है।

toStringगैर-कानूनी रूप से लागू करना बहुत बेकार होगा, क्योंकि केवल एक चीज जो वापस आ सकती है वह है ऑब्जेक्ट का पता, जो बहुत उपयोगकर्ता-अनफ्रेंडली है, और जिसे कॉल करने वाले के पास जावा के विपरीत पहले से ही एक्सेस है।
इसी तरह, एक nonvirtual equalsया hashCodeकेवल वस्तुओं की तुलना करने के लिए पते का उपयोग कर सकता है, जो फिर से बहुत बेकार है और अक्सर एकमुश्त गलत भी है - जावा के विपरीत, वस्तुओं को अक्सर C ++ में कॉपी किया जाता है, और इसलिए किसी वस्तु की "पहचान" को भेदना भी नहीं है हमेशा सार्थक या उपयोगी। (उदाहरण के लिए intवास्तव में इसके मूल्य के अलावा कोई पहचान नहीं होनी चाहिए ... समान मूल्य के दो पूर्णांक समान होने चाहिए।)


इस मुद्दे और माइक नाकिस द्वारा उल्लेखित नाजुक आधार वर्ग के मुद्दे के संबंध में, दिलचस्प शोध / जावा में इसे ठीक करने के प्रस्ताव पर ध्यान दें, मूल रूप से सभी तरीकों को आंतरिक रूप से बनाकर (अर्थात जब एक ही कक्षा से बुलाया जाता है) गैर-आभासी लेकिन उनके आभासी व्यवहार को ध्यान में रखते हुए बाहरी रूप से कहा जाता है; पुराने / मानक व्यवहार (यानी हर जगह आभासी) पाने के लिए प्रस्ताव ने एक नया openकीवर्ड पेश किया । मुझे नहीं लगता कि यह कहीं भी कुछ कागजों से परे चला गया।
सीटी

है कि कागज के बारे में एक थोड़ा और अधिक चर्चा में पाया जा सकता lambda-the-ultimate.org/classic/message12271.html
सीटी

एक सामान्य आधार वर्ग होने से किसी भी व्यक्ति shared_ptr<Foo> को यह देखने के लिए परीक्षण करना संभव होगा कि क्या यह भी shared_ptr<Bar>(या अन्य सूचक प्रकारों के साथ), भले ही Fooऔर Barअसंबंधित वर्ग हैं जो एक दूसरे के बारे में कुछ नहीं जानते हैं। आवश्यक है कि इस तरह के काम "कच्चे पॉइंटर्स" के साथ काम करें, यह देखते हुए कि इस तरह की चीजों का उपयोग कैसे किया जाता है, यह महंगा होगा, लेकिन उन चीजों के लिए जो वैसे भी ढेर हो जाने वाले हैं, अतिरिक्त लागत न्यूनतम होगी।
सुपरकैट

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

16

एक रूट ऑब्जेक्ट होने से आप बहुत अधिक अदायगी के बिना क्या कर सकते हैं और कंपाइलर क्या कर सकते हैं।

एक सामान्य रूट क्लास कंटेनर-कुछ भी बनाना संभव बनाता है और वे जो कुछ भी होते हैं उसे निकालते हैं dynamic_cast, लेकिन अगर आपको कंटेनर-की-कुछ भी ज़रूरत है, तो कुछ सामान्य रूट क्लास के बिना भीboost::any कर सकते हैं । और यह भी आदिम का समर्थन करता है - यह भी छोटे बफर अनुकूलन का समर्थन कर सकते हैं और उन्हें जावा पार्लेंस में लगभग "अनबॉक्स" छोड़ सकते हैं।boost::any

C ++ मान प्रकारों पर समर्थन और संपन्न करता है। दोनों शाब्दिक, और प्रोग्रामर लिखित मूल्य प्रकार। C ++ कंटेनर कुशलतापूर्वक स्टोर करते हैं, सॉर्ट करते हैं, मूल्य प्रकारों का उपभोग करते हैं और उत्पादन करते हैं।

वंशानुक्रम, विशेष रूप से अखंड विरासत जावा शैली के आधार वर्गों की तरह, फ्री-स्टोर आधारित "पॉइंटर" या "संदर्भ" प्रकार की आवश्यकता होती है। डेटा के लिए आपका हैंडल / पॉइंटर / संदर्भ क्लास के इंटरफेस के लिए एक पॉइंटर रखता है, और पॉलीमॉर्फिक कुछ और का प्रतिनिधित्व कर सकता है।

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

लगभग हमेशा आप कॉलिंग साइट पर या इसका उपयोग करने वाले कोड में "यह एक वस्तु है" की तुलना में एक प्रकार के बारे में अधिक जानते हैं।

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

मान लीजिए कि आपके पास कुछ पुस्तकालय हैं, जहाँ आप चाहते हैं कि सब कुछ क्रमबद्ध हो। आधार वर्ग के लिए एक दृष्टिकोण है:

struct serialization_friendly {
  virtual void write_to( my_buffer* ) const = 0;
  virtual void read_from( my_buffer const* ) = 0;
  virtual ~serialization_friendly() {}
};

अब आपके द्वारा लिखा गया हर कोड हो सकता है serialization_friendly

void serialize( my_buffer* b, serialization_friendly const* x ) {
  if (x) x->write_to(b);
}

ए को छोड़कर std::vector, इसलिए अब आपको हर कंटेनर को लिखना होगा। और उन पूर्णांकों से नहीं जो आपको उस ग्रंथालय पुस्तकालय से मिले थे। और ऐसा नहीं है कि आपने लिखा है कि आपको नहीं लगता कि क्रमबद्धता की आवश्यकता है। और न एक tuple, या एक intया एक double, या एकstd::ptrdiff_t

हम एक और तरीका अपनाते हैं:

void write_to( my_buffer* b, int x ) {
  b->write_integer(x);
}    
template<class T,
  class=std::enable_if_t< void_t<
    std::declval<T const*>()->write_to( std::declval<my_buffer*>()
  > >
>
void write_to( my_buffer* b, T const* x ) {
  if (x) x->write_to(b);
}
template<class T>
void serialize( my_buffer* b, T const& t ) {
  write_to( b, t );
}

जिसमें कुछ भी हो, कुछ भी नहीं, प्रतीत होता है। सिवाय अब हम किसी प्रकार या किसी विधि के नामस्थान में एक नि: शुल्क फ़ंक्शन के रूप में write_toओवरराइड करके विस्तार कर सकते हैं write_to

हम टाइप इरेज़र कोड भी लिख सकते हैं:

namespace details {
  struct can_serialize_pimpl {
    virtual void write_to( my_buffer* ) const = 0;
    virtual void read_from( my_buffer const* ) = 0;
    virtual ~can_serialize_pimpl() {}
  };
}
struct can_serialize {
  void write_to( my_buffer* b ) const { pImpl->write_to(b); }
  void read_from( my_buffer const* b ) { pImpl->read_from(b); }
  std::unique_ptr<details::can_serialize_pimpl> pImpl;
  template<class T> can_serialize(T&&);
};
namespace details { 
  template<class T>
  struct can_serialize : can_serialize_pimpl {
    std::decay_t<T>* t;
    void write_to( my_buffer*b ) const final override {
      serialize( b, std::forward<T>(*t) );
    }
    void read_from( my_buffer const* ) final override {
      deserialize( b, std::forward<T>(*t) );
    }
    can_serialize(T&& in):t(&in) {}
  };
}
template<class T> can_serialize::can_serialize<T>(T&&t):pImpl(
  std::make_unique<details::can_serialize<T>>( std::forward<T>(t) );
) {}

और अब हम एक अनियंत्रित प्रकार और ऑटो-बॉक्स को एक can_serializeइंटरफ़ेस में ले जा सकते हैं जो आपको serializeवर्चुअल इंटरफ़ेस के माध्यम से बाद के बिंदु पर आह्वान करने देता है।

इसलिए:

void writer_thingy( can_serialize s );

एक ऐसा फंक्शन है जो किसी भी चीज़ को ले सकता है, जिसके बदले वह सीरियल कर सकता है

void writer_thingy( serialization_friendly const* s );

और पहले, दूसरे के विपरीत, यह संभाल सकता है int,std::vector<std::vector<Bob>> स्वचालित रूप से।

इसे लिखने में ज्यादा समय नहीं लगा, खासकर इसलिए क्योंकि इस तरह की चीज एक ऐसी चीज है जिसे आप शायद ही कभी करना चाहते हैं, लेकिन हमें आधार प्रकार की आवश्यकता के बिना किसी भी चीज को उपचार योग्य बनाने की क्षमता प्राप्त हुई।

क्या अधिक है, अब हम std::vector<T>केवल ओवरराइड करके एक प्रथम श्रेणी के नागरिक के रूप में क्रमिक रूप से परिवर्तन कर सकते हैं write_to( my_buffer*, std::vector<T> const& )- उस अधिभार के साथ, इसे एक को दिया जा सकता है can_serializeऔर std::vectorएक जीवंतता में संग्रहीत हो जाता है और इसके द्वारा एक्सेस किया जाता है .write_to

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

जब प्रकार वास्तव में उनकी पहचान होते हैं, और आप जानते हैं कि वे क्या हैं, तो अनुकूलन के अवसर कम हो जाते हैं। डेटा स्थानीय रूप से और संक्रामक रूप से संग्रहीत किया जाता है (जो आधुनिक प्रोसेसर पर कैश मित्रता के लिए अत्यधिक महत्वपूर्ण है), संकलक आसानी से समझ सकते हैं कि एक दिया गया ऑपरेशन क्या करता है (इसके बजाय एक अपारदर्शी आभासी विधि सूचक होने के लिए इसे कूदना पड़ता है, अज्ञात कोड के लिए अग्रणी पर दूसरी तरफ) जो निर्देश को पुन: व्यवस्थित करने देता है, और कम गोल खूंटे को गोल छेद में अंकित किया जाता है।


8

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


1
हीरे की समस्या का एक समाधान यह है कि सभी प्रकार के जो गैर-वस्तुतः आधार प्रकार प्राप्त करते हैं कई मार्गों के माध्यम से उस आधार प्रकार के सभी आभासी सदस्यों को ओवरराइड करते हैं; यदि शुरू से ही एक सामान्य आधार प्रकार भाषा में बनाया गया था, तो एक संकलक स्वतः-वैध उत्पन्न कर सकता है (हालांकि आवश्यक रूप से प्रभावशाली नहीं) डिफ़ॉल्ट परीक्षाएं।
सुपरकैट

5

वास्तव में Microsofts प्रारंभिक C ++ कंपाइलर और लाइब्रेरीज़ (मुझे विजुअल C ++, 16 बिट के बारे में पता है) में इस तरह का एक वर्ग था CObject

हालाँकि आपको यह जानना होगा कि उस समय "टेम्पलेट" इस सरल C ++ कंपाइलर द्वारा समर्थित नहीं थे इसलिए कक्षाएं std::vector<class T>संभव नहीं थीं। इसके बजाय "वेक्टर" कार्यान्वयन केवल एक प्रकार की कक्षा को संभाल सकता है इसलिए एक वर्ग था जो std::vector<CObject>आज के लिए तुलनीय है। क्योंकि CObjectलगभग सभी वर्गों का आधार वर्ग था (दुर्भाग्य से CString- stringआधुनिक संकलक के बराबर ) आप लगभग सभी प्रकार की वस्तुओं के भंडारण के लिए इस वर्ग का उपयोग कर सकते थे।

क्योंकि आधुनिक कंपाइलर "जेनेरिक बेस क्लास" के इस उपयोग के मामले को समर्थन नहीं देते हैं।

आपको इस तथ्य के बारे में सोचना होगा कि इस तरह के जेनेरिक बेस क्लास का उपयोग करने पर (थोड़ी) मेमोरी और रनटाइम का खर्च आएगा - उदाहरण के लिए कंस्ट्रक्टर को कॉल करना। इसलिए ऐसी कक्षा का उपयोग करते समय कमियां हैं लेकिन कम से कम आधुनिक सी ++ कंपाइलर का उपयोग करते समय ऐसी कक्षा के लिए लगभग कोई उपयोग का मामला नहीं है।


3
क्या वह MFC है? [टिप्पणी पैडिंग]
21

3
यह वास्तव में MFC है। OO डिजाइन का एक चमकदार बीकन जिसने दुनिया को दिखाया कि चीजों को कैसे किया जाना चाहिए। ओह, रुको ...
gbjbaanb

4
@ जीबीजबांब टर्बो पास्कल और टर्बो सी ++ का अपना TObjectएमएफसी होने से पहले ही अस्तित्व में था। डिज़ाइन के उस हिस्से के लिए Microsoft को दोष न दें, यह उस समय के सभी लोगों को बहुत अच्छा लगता था।
hvd

टेम्प्लेट से पहले भी, C ++ में स्मॉलटाक को लिखने की कोशिश से भयानक परिणाम उत्पन्न हुए।
JDługosz

@hvd फिर भी, MFC वस्तु-उन्मुख डिज़ाइन का एक बहुत खराब उदाहरण था, जो बोरलैंड द्वारा निर्मित कुछ भी था।
जूल्स

5

मैं एक और कारण बताने जा रहा हूं जो जावा से आता है।

क्योंकि आप बॉयलर-प्लेट के एक गुच्छा के बिना कम से कम सब कुछ के लिए एक बेस-क्लास नहीं बना सकते ।

आप अपने स्वयं के वर्गों के लिए इसके साथ दूर हो सकते हैं - लेकिन आप शायद पाएंगे कि आप बहुत सारे कोड डुप्लिकेट कर रहे हैं। उदाहरण के लिए "मैं इसका उपयोग नहीं कर सकता std::vectorक्योंकि यह लागू नहीं होता है IObject- मैं बेहतर एक नया व्युत्पन्न बनाता हूं IVectorObjectजो सही काम करता है ..."।

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

अब अगर यह भाषा में बनाया गया था तुम जैसी चीजों को रखना होगा Integerऔर intभ्रम की स्थिति जावा में है, या भाषा वाक्य रचना के लिए एक बड़ा परिवर्तन। (माइंड यू मुझे लगता है कि कुछ अन्य भाषाओं ने इसे हर प्रकार में बनाने के साथ एक अच्छा काम किया है - रूबी एक बेहतर उदाहरण की तरह लगता है)

यह भी ध्यान दें कि यदि आपका बेस क्लास रन-टाइम पॉलीमॉर्फिक नहीं है (यानी वर्चुअल फ़ंक्शंस का उपयोग करके) तो आपको फ्रेमवर्क जैसे लक्षणों का उपयोग करने से समान लाभ मिल सकता है।

उदाहरण के लिए, इसके बजाय .toString()आपके पास निम्नलिखित हो सकते हैं: (नोट: मुझे पता है कि आप मौजूदा पुस्तकालयों आदि का उपयोग करके इस भोजनालय को कर सकते हैं, इसका सिर्फ एक उदाहरण है।)

template<typename T>
struct ToStringTrait;

template<typename T> 
std::string toString(const T & t) {
  return ToStringTrait<T>::toString(t);
}

template<>
struct ToStringTrait<int> {
  std::string toString(int v) {
    return itoa(v);
  }
}

template<typename T>
struct ToStringTrait<std::vector<T>> {
  std::string toString(const std::vector<T> &v) {
    std::stringstream ss;
    ss<<"{";
    for(int i=0; i<v.size(); ++i) {
      ss<<toString(v[i]);
    }
    ss<<"}";
    return ss.str();
  }
}

3

तर्कपूर्ण रूप से "शून्य" एक सार्वभौमिक आधार वर्ग की बहुत सी भूमिकाएं पूरी करता है। आप किसी भी पॉइंटर को कास्ट कर सकते हैं void*। फिर आप उन बिंदुओं की तुलना कर सकते हैं। आप static_castमूल वर्ग में वापस आ सकते हैं ।

हालांकि आप क्या नहीं कर सकते कर के साथ voidहै जो आप के साथ कर सकते Objectउपयोग RTTI तुम सच में है वस्तु की किस प्रकार यह पता लगाने की है। यह अंततः नीचे है कि कैसे C ++ में सभी ऑब्जेक्ट्स में RTTI नहीं है, और वास्तव में यह शून्य-चौड़ाई वाली वस्तुओं के लिए संभव है।


1
केवल शून्य-चौड़ाई वाले बेसकैप सब -जेक्ट, सामान्य नहीं।
Deduplicator

@Deduplicator अद्यतन के माध्यम से, C ++ 17 जोड़ता है [[no_unique_address]], जिसका उपयोग संकलक द्वारा सदस्य उपविषयों को शून्य चौड़ाई देने के लिए किया जा सकता है।
अंडरस्कोर_ड

1
@underscore_d का मतलब है कि आपने C ++ 20 के लिए योजना बनाई है, [[no_unique_address]]संकलनकर्ता को EBO सदस्य-चर की अनुमति देगा।
Deduplicator

@ डुप्लिकेटर वूप्स, यूप। मैंने पहले ही C ++ 17 का उपयोग शुरू कर दिया था, लेकिन मुझे लगता है कि मुझे अभी भी लगता है कि यह वास्तव में है की तुलना में अधिक अत्याधुनिक है!
अंडरस्कोर_ड

2

जावा डिजाइन दर्शन लेता है कि अपरिभाषित व्यवहार मौजूद नहीं होना चाहिए । कोड जैसे:

Cat felix = GetCat();
Woofer Rover = (Woofer)felix;
Rover.woof();

यह परीक्षण करेगा कि क्या उस इम्प्लीमेंट इंटरफेस felixका उपप्रकार रखता है ; यदि ऐसा होता है, तो यह कलाकारों का प्रदर्शन करेगा और आह्वान करेगा और यदि ऐसा नहीं होता है, तो यह एक अपवाद फेंक देगा। कोड का व्यवहार पूरी तरह से परिभाषित है कि क्या लागू होता है या नहींCatWooferwoof()felixWoofer

C ++ दर्शन लेता है कि अगर किसी प्रोग्राम को कुछ ऑपरेशन का प्रयास नहीं करना चाहिए, तो इससे कोई फर्क नहीं पड़ता कि उत्पन्न कोड क्या होगा यदि उस ऑपरेशन का प्रयास किया गया था, और कंप्यूटर को उन मामलों में व्यवहार में बाधा डालने के लिए समय बर्बाद नहीं करना चाहिए जो "चाहिए" कभी नहीं उठता। C ++ में, एक को कास्ट करने के लिए उपयुक्त इनडायरेक्शन ऑपरेटरों को जोड़ने के *Catलिए *Woofer, कोड परिभाषित व्यवहार प्राप्त करेगा जब कलाकारों को वैध है, लेकिन अपरिभाषित व्यवहार नहीं होने पर

चीजों के लिए एक सामान्य आधार प्रकार होने से उस आधार प्रकार के डेरिवेटिव के बीच जातियों को मान्य करना संभव हो जाता है, और कोशिश-कास्ट संचालन करने के लिए भी होता है, लेकिन जाति को मान्य करना केवल यह मानने की तुलना में अधिक महंगा है कि वे वैध हैं और कुछ भी बुरा नहीं होने की उम्मीद करते हैं। C ++ दर्शन यह होगा कि इस तरह के सत्यापन के लिए "आपको किसी चीज़ के लिए भुगतान करने की आवश्यकता होती है [आमतौर पर] इसकी आवश्यकता नहीं है"।

एक और समस्या जो सी ++ से संबंधित है, लेकिन एक नई भाषा के लिए कोई समस्या नहीं होगी, यह है कि यदि कई प्रोग्रामर प्रत्येक एक सामान्य आधार बनाते हैं, तो अपनी स्वयं की कक्षाएं उसी से प्राप्त करें, और उस सामान्य आधार वर्ग की चीजों के साथ काम करने के लिए कोड लिखें, ऐसे कोड प्रोग्रामर द्वारा विकसित वस्तुओं के साथ काम करने में असमर्थ होंगे, जिन्होंने एक अलग आधार वर्ग का उपयोग किया था। यदि एक नई भाषा के लिए आवश्यक है कि सभी हीप ऑब्जेक्ट्स में एक सामान्य हेडर प्रारूप हो, और उसने कभी भी उन ढेर वस्तुओं की अनुमति नहीं दी है, जो नहीं हुई, तो ऐसी विधि जिसके लिए ऐसे हेडर के साथ ढेर ऑब्जेक्ट के संदर्भ की आवश्यकता होती है, किसी भी हीप ऑब्जेक्ट के संदर्भ को स्वीकार करेगा। कभी भी बना सकता है।

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

परिशिष्ट

टेम्प्लेट का उपयोग करना, "मनमाने ढंग से ऑब्जेक्ट धारक" को परिभाषित करना और उसमें निहित वस्तु के प्रकार के बारे में पूछना संभव है; बूस्ट पैकेज में ऐसी चीज़ होती है जिसे कहा जाता है any। इस प्रकार, भले ही C ++ में मानक "कुछ भी करने के लिए" प्रकार-जांच योग्य संदर्भ नहीं है, फिर भी इसे बनाना संभव है। यह भाषा मानक में कुछ न होने के कारण, विभिन्न प्रोग्रामर के कार्यान्वयन के बीच असंगतता को हल करने में समस्या का समाधान नहीं करता है, लेकिन यह बताता है कि आधार प्रकार न होने से C ++ कैसे प्राप्त होता है, जिसमें से सबकुछ प्राप्त होता है: इसे बनाना संभव कुछ ऐसा जो एक कार्य करता है।


वह कलाकार C ++ , Java और C # में संकलन समय पर विफल हो जाता है
सहस्राब्दी

1
@milleniumbug: यदि Wooferकोई इंटरफ़ेस है और अंतर्निहित Catहै, तो कास्ट वैध होगा क्योंकि वहाँ मौजूद हो सकता है (यदि अभी नहीं, संभवतः भविष्य में) WoofingCatजो एक से विरासत में मिला Catऔर लागू होता है Woofer। ध्यान दें कि जावा संकलन / जोड़ने मॉडल के तहत, एक के निर्माण WoofingCatके लिए स्रोत कोड के लिए उपयोग की आवश्यकता नहीं होगी Catऔर न ही Woofer
सुपरकैट

3
C ++ में डायनेमिक_कास्ट है , जो ठीक से एक से कास्ट करने की कोशिश कर रहा Catहै Wooferऔर इस सवाल का जवाब देगा "क्या आप एक्स टाइप करने के लिए कन्वर्टिबल हैं"। C ++ आपको एक कास्ट को बाध्य करने की अनुमति देगा, हे, कारण, शायद आप वास्तव में जानते हैं कि आप क्या कर रहे हैं, लेकिन यह आपकी मदद भी करेगा यदि आप वास्तव में क्या करने का मतलब नहीं है।
Rob K

2
@ रोब: आप पाठ्यक्रम के वाक्य रचना के बारे में सही हैं; mea gupa। मैं डायनामिक_कास्ट के बारे में थोड़ा अधिक पढ़ रहा हूं और ऐसा लगता है कि आधुनिक सी ++ में सभी पॉलीमॉर्फिक ऑब्जेक्ट हैं जो किसी भी क्षेत्र (ओं) के साथ एक आधार "पॉलीमॉर्फिक ऑब्जेक्ट" बेस क्लास से प्राप्त होते हैं जो ऑब्जेक्ट के प्रकार (आमतौर पर एक व्यवहार्य) की पहचान करने के लिए आवश्यक हैं सूचक, हालांकि यह एक कार्यान्वयन विवरण है)। C ++ उस तरह से बहुरूपी वर्गों का वर्णन नहीं करता है, लेकिन एक पॉइंटर को dynamic_castपास करने से व्यवहार को परिभाषित किया जाएगा यदि यह एक
बहुरूपिक वस्तु की ओर इशारा

2
... सभी बहुरूपी वस्तुएं कुछ सूचनाओं को एक ही लेआउट के साथ संग्रहित करती हैं, और सभी एक व्यवहार का समर्थन करती हैं जो गैर-बहुरूपी वस्तुओं द्वारा समर्थित नहीं है; मेरे दिमाग में, इसका मतलब है कि वे व्यवहार करते हैं जैसे कि वे एक सामान्य आधार से निकलते हैं, चाहे भाषा परिभाषा ऐसी शब्दावली का उपयोग करती हो या नहीं।
सुपरकैट

1

सिम्बियन C ++ ने वास्तव में सभी वस्तुओं के लिए एक सार्वभौमिक आधार वर्ग, CBase किया था, जो एक विशेष तरीके से व्यवहार करते थे (मुख्य रूप से यदि वे ढेर आवंटित करते हैं)। इसने एक आभासी विध्वंसक प्रदान किया, निर्माण पर कक्षा की मेमोरी को शून्य कर दिया और कॉपी कंस्ट्रक्टर को छुपा दिया।

इसके पीछे तर्क यह था कि यह एम्बेडेड सिस्टम और C ++ कंपाइलर के लिए एक भाषा थी और चश्मा वास्तव में 10 साल पहले वास्तव में बकवास थे।

सभी वर्गों को यह विरासत में नहीं मिला, केवल कुछ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.