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