संरक्षित विरासत के विपरीत, C ++ निजी विरासत ने मुख्यधारा C ++ के विकास में अपना रास्ता खोज लिया। हालाँकि, मुझे अभी भी इसके लिए एक अच्छा उपयोग नहीं मिला है।
आप लोग इसका उपयोग कब करते हैं?
संरक्षित विरासत के विपरीत, C ++ निजी विरासत ने मुख्यधारा C ++ के विकास में अपना रास्ता खोज लिया। हालाँकि, मुझे अभी भी इसके लिए एक अच्छा उपयोग नहीं मिला है।
आप लोग इसका उपयोग कब करते हैं?
जवाबों:
उत्तर स्वीकृति के बाद ध्यान दें: यह पूर्ण उत्तर नहीं है। यदि आप प्रश्न में रुचि रखते हैं तो अन्य उत्तर यहां (वैचारिक रूप से) और यहां (सिद्धांत और व्यावहारिक दोनों ) पढ़ें । यह सिर्फ एक फैंसी चाल है जिसे निजी विरासत के साथ हासिल किया जा सकता है। जबकि यह फैंसी है यह सवाल का जवाब नहीं है।
सी ++ एफएक्यू (अन्य टिप्पणियों में जुड़े) में दिखाए गए सिर्फ निजी विरासत के मूल उपयोग के अलावा, आप एक वर्ग (.NET शब्दावली) को सील करने या एक वर्ग फाइनल (जावा शब्दावली में) बनाने के लिए निजी और आभासी विरासत के संयोजन का उपयोग कर सकते हैं । यह एक सामान्य उपयोग नहीं है, लेकिन वैसे भी मुझे यह दिलचस्प लगा:
class ClassSealer {
private:
friend class Sealed;
ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{
// ...
};
class FailsToDerive : public Sealed
{
// Cannot be instantiated
};
मुहरबंद को तत्काल किया जा सकता है। यह क्लाससेलर से प्राप्त होता है और निजी कंस्ट्रक्टर को सीधे कॉल कर सकता है क्योंकि यह एक दोस्त है।
FailsToDerive संकलन नहीं के रूप में यह कॉल करना होगा ClassSealer निर्माता सीधे (आभासी विरासत आवश्यकता), लेकिन यह उस में निजी है नहीं के रूप में कर सकते हैं सील वर्ग और इस मामले में FailsToDerive के एक दोस्त नहीं है ClassSealer ।
संपादित करें
टिप्पणियों में यह उल्लेख किया गया था कि सीआरटीपी का उपयोग करते समय इसे सामान्य नहीं बनाया जा सकता था। C ++ 11 मानक टेम्पलेट तर्कों से दोस्ती करने के लिए एक अलग सिंटैक्स प्रदान करके उस सीमा को हटा देता है:
template <typename T>
class Seal {
friend T; // not: friend class T!!!
Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...
बेशक यह सब लूट है, क्योंकि C ++ 11 final
वास्तव में इस उद्देश्य के लिए एक प्रासंगिक कीवर्ड प्रदान करता है :
class Sealed final // ...
मैं इसका हर समय उपयोग करता हूं। मेरे सिर के ऊपर से कुछ उदाहरण:
एक विशिष्ट उदाहरण STL कंटेनर से निजी रूप से प्राप्त हो रहा है:
class MyVector : private vector<int>
{
public:
// Using declarations expose the few functions my clients need
// without a load of forwarding functions.
using vector<int>::push_back;
// etc...
};
push_back
, MyVector
तो उन्हें मुफ्त में मिलता है।
template<typename... Args> constexpr decltype(auto) f(Args && ... args) noexcept(noexcept(std::declval<Base &>().f(std::forward<Args>(args)...)) and std::is_nothrow_move_constructible<decltype(std::declval<Base &>().f(std::forward<Args>(args)...))>) { return m_base.f(std::forward<Args>(args)...); }
या आप का उपयोग कर लिख सकते हैं Base::f;
। आप की कार्यक्षमता और लचीलापन है कि निजी विरासत के सबसे चाहते हैं और एक तो using
बयान आप देता है, तो आप प्रत्येक कार्य के लिए है कि राक्षस है (और के बारे में भूल नहीं है const
और volatile
भार के!)।
निजी उत्तराधिकार का विहित उपयोग "संबंध के संदर्भ में लागू किया गया है (स्कॉट वर्कर्स के लिए धन्यवाद '' इस शब्द के लिए प्रभावी C ++ ')। दूसरे शब्दों में, विरासत वाले वर्ग के बाहरी इंटरफ़ेस का विरासत वाले वर्ग से कोई (दृश्य) संबंध नहीं है, लेकिन यह अपनी कार्यक्षमता को लागू करने के लिए आंतरिक रूप से इसका उपयोग करता है।
निजी विरासत का एक उपयोगी उपयोग तब होता है जब आपके पास एक वर्ग होता है जो एक इंटरफ़ेस को लागू करता है, जो तब किसी अन्य ऑब्जेक्ट के साथ पंजीकृत होता है। आप उस इंटरफ़ेस को निजी बनाते हैं ताकि कक्षा को खुद को पंजीकृत करना पड़े और केवल विशिष्ट वस्तु जो कि उसके साथ पंजीकृत हो, उन कार्यों का उपयोग कर सके।
उदाहरण के लिए:
class FooInterface
{
public:
virtual void DoSomething() = 0;
};
class FooUser
{
public:
bool RegisterFooInterface(FooInterface* aInterface);
};
class FooImplementer : private FooInterface
{
public:
explicit FooImplementer(FooUser& aUser)
{
aUser.RegisterFooInterface(this);
}
private:
virtual void DoSomething() { ... }
};
इसलिए FooUser वर्ग FooInterface इंटरफ़ेस के माध्यम से FooImplementer के निजी तरीकों को कॉल कर सकता है, जबकि अन्य अन्य वर्ग नहीं कर सकते। यह विशिष्ट कॉलबैक से निपटने के लिए एक बढ़िया पैटर्न है जिसे इंटरफेस के रूप में परिभाषित किया गया है।
मुझे लगता है कि C ++ FAQ Lite से महत्वपूर्ण खंड है:
निजी विरासत के लिए एक वैध, दीर्घकालिक उपयोग तब होता है जब आप एक वर्ग फ्रेड का निर्माण करना चाहते हैं जो एक वर्ग विल्मा में कोड का उपयोग करता है, और वर्ग विल्मा से कोड को आपके नए वर्ग, फ्रेड से सदस्य कार्यों को आमंत्रित करने की आवश्यकता होती है। इस मामले में, फ्रेड विल्मा में गैर-आभासी कॉल करता है, और विल्मा (आमतौर पर शुद्ध वर्चुअल) अपने आप में कॉल करता है, जो कि फ्रेड द्वारा ओवरराइड किया जाता है। रचना के साथ ऐसा करना बहुत कठिन होगा।
यदि संदेह है, तो आपको निजी विरासत पर रचना पसंद करनी चाहिए।
मुझे यह इंटरफेस (अर्थात अमूर्त वर्ग) के लिए उपयोगी लगता है जो मुझे विरासत में मिला है जहाँ मैं इंटरफ़ेस को छूने के लिए अन्य कोड नहीं चाहता (केवल विरासत वर्ग)।
[एक उदाहरण में संपादित]
ऊपर से जुड़ा उदाहरण लें । कहते हुए की
[...] वर्ग विल्मा को आपके नए वर्ग, फ्रेड से सदस्य कार्यों को आमंत्रित करने की आवश्यकता है।
कहने का मतलब यह है कि विल्मा को फ्रेड की आवश्यकता है कि वह कुछ सदस्य कार्यों को करने में सक्षम हो, या, बल्कि यह कह रहा है कि विल्मा एक इंटरफ़ेस है । इसलिए, जैसा कि उदाहरण में बताया गया है
निजी विरासत बुराई नहीं है; यह सिर्फ बनाए रखने के लिए अधिक महंगा है, क्योंकि यह संभावना बढ़ जाती है कि कोई व्यक्ति ऐसा कुछ बदलेगा जो आपके कोड को तोड़ देगा।
प्रोग्रामर के वांछित प्रभाव पर टिप्पणियाँ हमारी इंटरफ़ेस आवश्यकताओं को पूरा करने या कोड को तोड़ने की आवश्यकता होती हैं। और, चूंकि fredCallsWilma () केवल दोस्तों से सुरक्षित है और व्युत्पन्न वर्ग इसे छू सकते हैं अर्थात एक विरासत में मिला इंटरफ़ेस (सार वर्ग) जिसे केवल विरासत वाले वर्ग (और मित्र) ही छू सकते हैं।
[एक अन्य उदाहरण में संपादित]
यह पृष्ठ निजी इंटरफ़ेस (अभी तक किसी अन्य कोण से) पर चर्चा करता है।
कभी-कभी जब मैं दूसरे के इंटरफेस में एक छोटे से इंटरफ़ेस (जैसे एक संग्रह) को उजागर करना चाहता हूं, तो मुझे निजी विरासत का उपयोग करना उपयोगी लगता है, जहां संग्रह कार्यान्वयन को आंतरिक कक्षाओं में एक समान तरीके से, एक्सपोजिंग क्लास की स्थिति तक पहुंच की आवश्यकता होती है। जावा।
class BigClass;
struct SomeCollection
{
iterator begin();
iterator end();
};
class BigClass : private SomeCollection
{
friend struct SomeCollection;
SomeCollection &GetThings() { return *this; }
};
फिर अगर SomeCollection को BigClass तक पहुंचने की आवश्यकता है, तो यह कर सकता है static_cast<BigClass *>(this)
। अंतरिक्ष में अतिरिक्त डेटा सदस्य रखने की आवश्यकता नहीं है।
BigClass
इस उदाहरण में आगे की घोषणा की कोई आवश्यकता नहीं है? मुझे यह दिलचस्प लगता है, लेकिन यह मेरे चेहरे में हैकिश चिल्लाता है।
मुझे निजी विरासत के लिए एक अच्छा आवेदन मिला, हालांकि इसका सीमित उपयोग है।
मान लीजिए आपको निम्नलिखित C API दिया गया है:
#ifdef __cplusplus
extern "C" {
#endif
typedef struct
{
/* raw owning pointer, it's C after all */
char const * name;
/* more variables that need resources
* ...
*/
} Widget;
Widget const * loadWidget();
void freeWidget(Widget const * widget);
#ifdef __cplusplus
} // end of extern "C"
#endif
अब आपका काम C ++ का उपयोग करके इस एपीआई को लागू करना है।
बेशक हम सी-ईश कार्यान्वयन शैली चुन सकते हैं जैसे:
Widget const * loadWidget()
{
auto result = std::make_unique<Widget>();
result->name = strdup("The Widget name");
// More similar assignments here
return result.release();
}
void freeWidget(Widget const * const widget)
{
free(result->name);
// More similar manual freeing of resources
delete widget;
}
लेकिन इसके कई नुकसान हैं:
struct
गलत को सेट करना आसान हैstruct
हमें C ++ का उपयोग करने की अनुमति है, इसलिए इसकी पूर्ण शक्तियों का उपयोग क्यों नहीं किया जाता है?
उपरोक्त समस्याएं मूल रूप से मैनुअल संसाधन प्रबंधन से जुड़ी हैं। समाधान जो मन में आता है वह प्रत्येक चर के लिए Widget
व्युत्पन्न वर्ग में संसाधन प्रबंधन उदाहरण से विरासत में मिला है WidgetImpl
:
class WidgetImpl : public Widget
{
public:
// Added bonus, Widget's members get default initialized
WidgetImpl()
: Widget()
{}
void setName(std::string newName)
{
m_nameResource = std::move(newName);
name = m_nameResource.c_str();
}
// More similar setters to follow
private:
std::string m_nameResource;
};
यह निम्नलिखित के लिए कार्यान्वयन को सरल बनाता है:
Widget const * loadWidget()
{
auto result = std::make_unique<WidgetImpl>();
result->setName("The Widget name");
// More similar setters here
return result.release();
}
void freeWidget(Widget const * const widget)
{
// No virtual destructor in the base class, thus static_cast must be used
delete static_cast<WidgetImpl const *>(widget);
}
इस तरह हमने उपरोक्त सभी समस्याओं को दूर किया। लेकिन एक ग्राहक अभी भी बसने के बारे में भूल सकता है WidgetImpl
और उसे असाइन कर सकता हैWidget
सदस्यों को सीधे सकता है।
उन Widget
सदस्यों को एनकैप्सुलेट करने के लिए हम निजी विरासत का उपयोग करते हैं। अफसोस की बात यह है कि अब हमें दोनों वर्गों के बीच दो अतिरिक्त कार्य करने होंगे:
class WidgetImpl : private Widget
{
public:
WidgetImpl()
: Widget()
{}
void setName(std::string newName)
{
m_nameResource = std::move(newName);
name = m_nameResource.c_str();
}
// More similar setters to follow
Widget const * toWidget() const
{
return static_cast<Widget const *>(this);
}
static void deleteWidget(Widget const * const widget)
{
delete static_cast<WidgetImpl const *>(widget);
}
private:
std::string m_nameResource;
};
यह निम्नलिखित अनुकूलन आवश्यक बनाता है:
Widget const * loadWidget()
{
auto widgetImpl = std::make_unique<WidgetImpl>();
widgetImpl->setName("The Widget name");
// More similar setters here
auto const result = widgetImpl->toWidget();
widgetImpl.release();
return result;
}
void freeWidget(Widget const * const widget)
{
WidgetImpl::deleteWidget(widget);
}
यह समाधान सभी समस्याओं को हल करता है। कोई मैनुअल मेमोरी प्रबंधन और Widget
अच्छी तरह से समझाया गया है ताकिWidgetImpl
कोई भी सार्वजनिक डेटा सदस्य न हो। गलत तरीके से उपयोग करने के लिए कार्यान्वयन को सही और कठिन (असंभव?) करना आसान बनाता है।
कोड स्निपेट कोलिरु पर एक संकलन उदाहरण है ।
यदि व्युत्पन्न वर्ग - को कोड का पुन: उपयोग करने की आवश्यकता है और - आप बेस क्लास को बदल नहीं सकते हैं और - एक लॉक के तहत आधार के सदस्यों का उपयोग करके अपने तरीकों की रक्षा कर रहे हैं।
तब आपको निजी विरासत का उपयोग करना चाहिए, अन्यथा आपको इस व्युत्पन्न वर्ग के माध्यम से निर्यात किए गए अनलॉक किए गए आधार तरीकों का खतरा है।
जब संबंध "निजी नहीं है" का उपयोग किया जाना है, लेकिन नई कक्षा को "मौजूदा वर्ग की अवधि में लागू किया जा सकता है" या "मौजूदा वर्ग की तरह नए वर्ग" का काम किया जा सकता है।
"सी ++ कोडिंग मानकों से आंद्रेई अलेक्जेंड्रेस्कु, हर्ब सटर" से उदाहरण: - विचार करें कि दो वर्ग स्क्वायर और आयत प्रत्येक में अपनी ऊंचाई और चौड़ाई निर्धारित करने के लिए आभासी कार्य हैं। तब स्क्वायर सही ढंग से आयत से नहीं निकल सकता है, क्योंकि कोड जो एक संशोधित आयत का उपयोग करता है, यह मान लेगा कि SetWidth ऊँचाई नहीं बदलता है (चाहे आयत स्पष्ट रूप से उस अनुबंध को दस्तावेज़ करता हो या नहीं), जबकि Square :: SetWidth उस अनुबंध और उसकी स्वयं की अशुद्धि को नहीं बदल सकता है। उसी समय। लेकिन आयत सही ढंग से स्क्वायर से विरासत में नहीं मिल सकती है, यदि स्क्वायर के ग्राहक उदाहरण के लिए मान लेते हैं कि स्क्वायर का क्षेत्रफल इसकी चौड़ाई है, या यदि वे कुछ अन्य संपत्ति पर निर्भर करते हैं जो आयत के लिए पकड़ में नहीं आती हैं।
एक वर्ग "है-एक" आयत (गणितीय) लेकिन एक वर्ग एक आयत (व्यवहार) नहीं है। नतीजतन, "is-a," के बजाय हम "काम-जैसा-ए" कहना पसंद करते हैं (या, यदि आप पसंद करते हैं, तो विवरण को गलतफहमी का खतरा कम करने के लिए "प्रयोग करने योग्य-जैसा")।
एक वर्ग एक आक्रमणकारी रखता है। निर्माणकर्ता द्वारा अपरिवर्तनीय की स्थापना की जाती है। हालाँकि, कई स्थितियों में ऑब्जेक्ट के प्रतिनिधित्व की स्थिति (जो आप नेटवर्क पर संचारित कर सकते हैं या फ़ाइल को सहेज सकते हैं - डीटीओ यदि आप चाहें तो) को देखने के लिए उपयोगी है। एग्रीगेटटाइप के संदर्भ में REST सबसे अच्छा किया जाता है। यदि आप सही हैं तो यह विशेष रूप से सच है। विचार करें:
struct QuadraticEquationState {
const double a;
const double b;
const double c;
// named ctors so aggregate construction is available,
// which is the default usage pattern
// add your favourite ctors - throwing, try, cps
static QuadraticEquationState read(std::istream& is);
static std::optional<QuadraticEquationState> try_read(std::istream& is);
template<typename Then, typename Else>
static std::common_type<
decltype(std::declval<Then>()(std::declval<QuadraticEquationState>()),
decltype(std::declval<Else>()())>::type // this is just then(qes) or els(qes)
if_read(std::istream& is, Then then, Else els);
};
// this works with QuadraticEquation as well by default
std::ostream& operator<<(std::ostream& os, const QuadraticEquationState& qes);
// no operator>> as we're const correct.
// we _might_ (not necessarily want) operator>> for optional<qes>
std::istream& operator>>(std::istream& is, std::optional<QuadraticEquationState>);
struct QuadraticEquationCache {
mutable std::optional<double> determinant_cache;
mutable std::optional<double> x1_cache;
mutable std::optional<double> x2_cache;
mutable std::optional<double> sum_of_x12_cache;
};
class QuadraticEquation : public QuadraticEquationState, // private if base is non-const
private QuadraticEquationCache {
public:
QuadraticEquation(QuadraticEquationState); // in general, might throw
QuadraticEquation(const double a, const double b, const double c);
QuadraticEquation(const std::string& str);
QuadraticEquation(const ExpressionTree& str); // might throw
}
इस बिंदु पर, आप केवल कंटेनरों में कैश के संग्रह को स्टोर कर सकते हैं और इसे निर्माण पर देख सकते हैं। अगर वहाँ कुछ असली प्रसंस्करण हैडी। ध्यान दें कि कैश क्यूई का हिस्सा है: क्यूई पर परिभाषित संचालन का मतलब हो सकता है कि कैश आंशिक रूप से पुन: प्रयोज्य है (जैसे, सी राशि को प्रभावित नहीं करता है); अभी तक, जब कोई कैश नहीं है, तो इसे देखने लायक है।
निजी विरासत लगभग हमेशा एक सदस्य द्वारा तैयार की जा सकती है (यदि आवश्यक हो तो आधार के संदर्भ में भंडारण)। यह हमेशा उस तरह से मॉडल करने के लिए इसके लायक नहीं है; कभी-कभी विरासत सबसे कुशल प्रतिनिधित्व है।
यदि आपको std::ostream
कुछ छोटे परिवर्तनों (जैसे इस प्रश्न में ) की आवश्यकता है, तो आपको इसकी आवश्यकता हो सकती है
MyStreambuf
जो std::streambuf
वहां से आए और वहां बदलाव लागू करेMyOStream
जो इससे उत्पन्न std::ostream
भी हो और इसके उदाहरण का प्रबंधन MyStreambuf
और निर्माण करने वाले के लिए सूचक को पास करेstd::ostream
पहला विचार वर्ग में MyStream
डेटा सदस्य के रूप में उदाहरण जोड़ना हो सकता है MyOStream
:
class MyOStream : public std::ostream
{
public:
MyOStream()
: std::basic_ostream{ &m_buf }
, m_buf{}
{}
private:
MyStreambuf m_buf;
};
लेकिन आधार वर्ग किसी भी डेटा के सदस्यों से पहले निर्माण कर रहे हैं ताकि आप एक नहीं अभी तक का निर्माण करने के लिए एक सूचक गुजर रहे हैं std::streambuf
उदाहरण के लिए std::ostream
जो अपरिभाषित व्यवहार है।
उक्त प्रश्न के बेन के उत्तर में समाधान प्रस्तावित है , बस पहले स्ट्रीम बफर से वारिस करें, फिर स्ट्रीम से और फिर स्ट्रीम को इनिशियलाइज़ करें this
:
class MyOStream : public MyStreamBuf, public std::ostream
{
public:
MyOStream()
: MyStreamBuf{}
, basic_ostream{ this }
{}
};
हालाँकि परिणामी वर्ग को एक std::streambuf
उदाहरण के रूप में भी इस्तेमाल किया जा सकता है जो आमतौर पर अवांछित है। निजी विरासत पर स्विच करना इस समस्या को हल करता है:
class MyOStream : private MyStreamBuf, public std::ostream
{
public:
MyOStream()
: MyStreamBuf{}
, basic_ostream{ this }
{}
};
सिर्फ इसलिए कि C ++ में एक सुविधा है, इसका मतलब यह नहीं है कि यह उपयोगी है या इसका उपयोग किया जाना चाहिए।
मैं कहूंगा कि आपको इसका इस्तेमाल बिल्कुल नहीं करना चाहिए।
यदि आप इसे वैसे भी उपयोग कर रहे हैं, तो ठीक है, आप मूल रूप से इनकैप्सुलेशन का उल्लंघन कर रहे हैं, और सामंजस्य कम कर रहे हैं। आप डेटा को एक कक्षा में रख रहे हैं, और उन तरीकों को जोड़ रहे हैं जो डेटा को दूसरे में जोड़ते हैं।
अन्य सी ++ फीचर्स की तरह, इसका उपयोग साइड इफेक्ट्स को प्राप्त करने के लिए किया जा सकता है जैसे कि क्लास को सील करना (जैसा कि ड्रिबीज़ के उत्तर में बताया गया है), लेकिन यह इसे एक अच्छी सुविधा नहीं बनाता है।