जब गैर-कॉन्स्टिट्यूशन निजी है, तो सार्वजनिक कॉन्स्ट विधि को क्यों नहीं कहा जाता है?


117

इस कोड पर विचार करें:

struct A
{
    void foo() const
    {
        std::cout << "const" << std::endl;
    }

    private:

        void foo()
        {
            std::cout << "non - const" << std::endl;
        }
};

int main()
{
    A a;
    a.foo();
}

संकलक त्रुटि है:

त्रुटि: 'शून्य ए :: फू ()' निजी है।

लेकिन जब मैं निजी को हटाता हूं तो यह काम करता है। जब गैर-कांस्टेबल निजी है, तो सार्वजनिक कॉन्स्ट विधि को क्यों नहीं कहा जाता है?

दूसरे शब्दों में, अभिगम नियंत्रण से पहले अधिभार संकल्प क्यों आता है? यह अजीब है। क्या आपको लगता है कि यह सुसंगत है? मेरा कोड काम करता है और फिर मैं एक विधि जोड़ता हूं, और मेरा कार्य कोड बिल्कुल भी संकलित नहीं करता है।


3
C ++ में, PIMPL मुहावरे के उपयोग जैसे अतिरिक्त प्रयास के बिना, कक्षा का कोई वास्तविक "निजी" हिस्सा नहीं है। यह सिर्फ समस्याओं में से एक है ("निजी" विधि अधिभार जोड़ना और संकलन पुराने कोड को तोड़ना मेरी पुस्तक में एक समस्या के रूप में गिना जाता है, भले ही यह एक ऐसा करने से बचने के लिए तुच्छ है) इसके कारण।
हाइड

क्या कोई वास्तविक जीवन कोड है जहां आप एक कांस्ट फ़ंक्शन को कॉल करने में सक्षम होने की उम्मीद करेंगे, लेकिन इसका गैर-कॉस्ट समकक्ष निजी इंटरफ़ेस का हिस्सा होगा? यह मुझे बुरा इंटरफ़ेस डिजाइन की तरह लगता है।
विंसेंट फोरमंड

जवाबों:


125

जब आप कॉल करते हैं a.foo();, तो उपयोग करने के लिए सबसे अच्छा फ़ंक्शन खोजने के लिए कंपाइलर ओवरलोड रिज़ॉल्यूशन से गुजरता है। जब यह बनाता है अधिभार सेट यह पाता है

void foo() const

तथा

void foo()

अब, चूंकि aनहीं है const, नॉन-कास्ट संस्करण सबसे अच्छा मैच है, इसलिए संकलक चुनता है void foo()। तब एक्सेस प्रतिबंध लगाए जाते हैं और आपको एक कंपाइलर त्रुटि मिलती है, चूंकि void foo()निजी है।

याद रखें, अधिभार संकल्प में यह 'सबसे अच्छा प्रयोग करने योग्य फ़ंक्शन नहीं मिल रहा है'। यह 'सबसे अच्छा काम खोजने और इसे इस्तेमाल करने की कोशिश' है। यदि यह एक्सेस प्रतिबंधों के कारण या हटाया नहीं जा सकता है, तो आपको एक कंपाइलर त्रुटि मिलती है।

दूसरे शब्दों में, अभिगम नियंत्रण से पहले अधिभार संकल्प क्यों आता है?

खैर, आइए देखें:

struct Base
{
    void foo() { std::cout << "Base\n"; }
};

struct Derived : Base
{
    void foo() { std::cout << "Derived\n"; }
};

struct Foo
{
    void foo(Base * b) { b->foo(); }
private:
    void foo(Derived * d) { d->foo(); }
};

int main()
{
    Derived d;
    Foo f;
    f.foo(&d);
}

अब कहते हैं कि मैं वास्तव में void foo(Derived * d)निजी बनाने का मतलब नहीं था । यदि अभिगम नियंत्रण पहले आया था, तो यह कार्यक्रम संकलित और चला Baseजाएगा और मुद्रित किया जाएगा। यह एक बड़े कोड बेस में नीचे ट्रैक करने के लिए बहुत कठिन हो सकता है। चूंकि अधिगम नियंत्रण के बाद अभिगम नियंत्रण आता है, मुझे एक अच्छा संकलक त्रुटि मिलती है जो मुझे बताती है कि मैं यह चाहता हूं कि इसे कॉल नहीं किया जा सके, और मैं बग को बहुत आसान पा सकता हूं।


क्या कोई कारण है कि अधिभार संकल्प के बाद अभिगम नियंत्रण क्यों है?
drake7707

3
@ drake7707 Wll जैसा कि मैं अपने कोड सैंपल में दिखाता हूं कि अगर एक्सेस कंट्रोल पहले आया था तो उपरोक्त कोड संकलित होगा, जो प्रोग्राम के शब्दार्थ को बदल देता है। आपके बारे में निश्चित नहीं है, लेकिन मेरे पास एक त्रुटि होगी और एक स्पष्ट कलाकारों को करने की आवश्यकता होगी यदि मैं चाहता था कि फ़ंक्शन निजी रहे तो एक निहित कास्ट और कोड चुपचाप "काम करता है"।
नाथनऑलिवर

"और एक स्पष्ट कलाकारों को करने की आवश्यकता है अगर मैं चाहता था कि फ़ंक्शन निजी बने रहे" - ऐसा लगता है कि यहां असली मुद्दा निहितार्थ है ... हालांकि दूसरी तरफ, यह विचार कि आप व्युत्पन्न वर्ग का भी उपयोग कर सकते हैं जैसा कि स्पष्ट है आधार वर्ग OO प्रतिमान की एक परिभाषित विशेषता है, है ना?
स्टीवन बक्स

35

अंतत: यह मानक में इस दावे के नीचे आता है कि अधिभार संकल्प को निष्पादित करते समय सुगमता पर ध्यान नहीं दिया जाना चाहिए । इस दावे को [ओवरमॉच] क्लाज 3 में पाया जा सकता है :

... जब अधिभार संकल्प सफल होता है, और सबसे अच्छा व्यवहार्य फ़ंक्शन सुलभ नहीं है (क्लॉज [वर्ग। अधिक]) उस संदर्भ में जिसमें इसका उपयोग किया जाता है, कार्यक्रम बीमार है।

और उसी खंड के नोट 1 में भी नोट करें :

[नोट: अधिभार संकल्प द्वारा चयनित फ़ंक्शन को संदर्भ के लिए उपयुक्त होने की गारंटी नहीं है। अन्य प्रतिबंध, जैसे कि फ़ंक्शन की पहुंच, कॉलिंग संदर्भ में इसका उपयोग बीमार बना सकते हैं। - अंतिम नोट]

क्यों के लिए, मैं संभावित प्रेरणा के एक जोड़े के बारे में सोच सकते हैं:

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

32

मान लीजिए कि अधिभार संकल्प से पहले अभिगम नियंत्रण आया था। प्रभावी रूप से, इसका मतलब होगा कि public/protected/privateपहुँच क्षमता के बजाय दृश्यता नियंत्रित।

स्ट्रॉस्ट्रुप द्वारा सी ++ के डिजाइन और विकास का खंड 2.10 इस पर एक मार्ग है जहां वह निम्नलिखित उदाहरण पर चर्चा करता है

int a; // global a

class X {
private:
    int a; // member X::a
};

class XX : public X {
    void f() { a = 1; } // which a?
};

Stroustrup उल्लेख मौजूदा नियमों (पहुँच से पहले दृश्यता) की एक लाभ यह है कि (अस्थायी) chaning है कि privateअंदर class Xमें public(जैसे डिबगिंग के प्रयोजनों के लिए) उपरोक्त कार्यक्रम के अर्थ में कोई चुप परिवर्तन है कि वहाँ (यानी है X::aकरने का प्रयास किया है दोनों मामलों में पहुँचा जा सकता है, जो उपरोक्त उदाहरण में एक एक्सेस त्रुटि देता है)। यदि public/protected/privateदृश्यता को नियंत्रित करेगा, तो कार्यक्रम का अर्थ बदल जाएगा (वैश्विक के aसाथ कहा जाएगा private, अन्यथा X::a)।

उसके बाद उन्होंने कहा कि उन्हें याद नहीं है कि यह स्पष्ट डिजाइन द्वारा किया गया था या प्रीप्रोसेसर तकनीक का एक साइड इफेक्ट था जिसका उपयोग सी को क्लासेस पूर्ववर्ती के साथ मानक सी ++ में लागू किया गया था।

यह आपके उदाहरण से कैसे संबंधित है? मूल रूप से क्योंकि मानक बनाया अधिभार संकल्प सामान्य नियम के अनुरूप है कि नाम लुकअप एक्सेस कंट्रोल से पहले आता है।

10.2 सदस्य का नाम लुकअप [class.member.lookup]

1 सदस्य नाम लुकअप एक वर्ग गुंजाइश (3.3.7) में एक नाम (आईडी-अभिव्यक्ति) का अर्थ निर्धारित करता है। नाम लुकअप में अस्पष्टता हो सकती है, जिस स्थिति में प्रोग्राम बीमार है। एक आईडी-एक्सप्रेशन के लिए, नाम लुकअप इस के वर्ग दायरे में शुरू होता है; एक योग्य-आईडी के लिए, नेस्टेम- स्पेसियर के दायरे में नाम देखने की शुरुआत होती है। अभिगम नियंत्रण (3.4, खण्ड 11) से पहले नाम देखने की क्रिया होती है

8 यदि एक अतिभारित फ़ंक्शन का नाम स्पष्ट रूप से पाया जाता है, अभिगम नियंत्रण से पहले ओवरलोडिंग रिज़ॉल्यूशन (13.3) भी होता है । एंबीगुएट्स को अक्सर एक नाम को उसके वर्ग के नाम के साथ क्वालीफाई करके हल किया जा सकता है।


23

चूंकि अंतर्निहित thisसूचक गैर- है const, इसलिए संकलक constपहले ए के पहले गैर- संस्करण की उपस्थिति के लिए जांच करेगाconst संस्करण ।

यदि आप स्पष्ट रूप से गैर- constएक को चिह्नित करते privateहैं, तो रिज़ॉल्यूशन विफल हो जाएगा, और कंपाइलर खोज जारी नहीं रखेगा।


क्या आपको लगता है कि यह सुसंगत है? मेरा कोड काम करता है और फिर मैं एक विधि जोड़ता हूं और मेरा कार्य कोड बिल्कुल भी संकलित नहीं होता है।
नारेक

मुझे ऐसा ही लगता है। अधिभार संकल्प जानबूझकर उधम मचाते है। मैंने कल एक ऐसे ही सवाल का जवाब दिया: stackoverflow.com/questions/39023325/…
बाथशीबा

5
@Narek मेरा मानना ​​है कि यह ठीक उसी तरह काम करता है जैसे ओवरलोड रिज़ॉल्यूशन में डिलीट किए गए फ़ंक्शंस कैसे करते हैं। यह सेट से सर्वश्रेष्ठ को चुनता है और फिर यह देखता है कि यह उपलब्ध नहीं है इसलिए आपको एक कंपाइलर त्रुटि मिलती है। यह सबसे अच्छा प्रयोग करने योग्य कार्य नहीं करता है लेकिन सबसे अच्छा कार्य करता है और फिर इसका उपयोग करने की कोशिश करता है।
नेथनऑलिवर

3
@Narek मुझे पहले यह भी आश्चर्य हुआ कि यह काम क्यों नहीं करता, लेकिन इस पर विचार करें: यदि आप सार्वजनिक समारोह को गैर कास्ट ऑब्जेक्ट के लिए भी चुना जाना चाहिए, तो आप कभी भी निजी फ़ंक्शन को कैसे कॉल करेंगे?
इडक्लेव 463035818

20

यह महत्वपूर्ण है कि होने वाली चीजों के क्रम को ध्यान में रखें, जो है:

  1. सभी व्यवहार्य कार्य खोजें।
  2. सबसे अच्छा व्यवहार्य समारोह चुनें।
  3. यदि वास्तव में एक सबसे अच्छा व्यवहार्य नहीं है, या यदि आप वास्तव में सबसे अच्छा व्यवहार्य फ़ंक्शन (एक्सेस उल्लंघन या फ़ंक्शन deleteडी होने के कारण) को कॉल नहीं कर सकते हैं , तो असफल रहें।

(३) के बाद होता है (२)। जो वास्तव में महत्वपूर्ण है, क्योंकि अन्यथा कार्य करना deleteडी या privateअर्थहीन की तरह हो जाएगा और इसके बारे में तर्क करना बहुत कठिन होगा।

इस मामले में:

  1. व्यवहार्य कार्य हैं A::foo()और A::foo() const
  2. सबसे अच्छा व्यवहार्य फ़ंक्शन है A::foo()क्योंकि उत्तरार्द्ध में निहित thisतर्क पर एक योग्यता रूपांतरण शामिल है ।
  3. लेकिन A::foo()यह privateआपके पास नहीं है, इसलिए कोड बीमार है।

1
कोई सोच सकता है कि "व्यवहार्य" में प्रासंगिक पहुंच प्रतिबंध शामिल होंगे। दूसरे शब्दों में, यह वर्ग के बाहर से एक निजी फ़ंक्शन को कॉल करने के लिए "व्यवहार्य" नहीं है, क्योंकि यह उस वर्ग के सार्वजनिक इंटरफ़ेस का हिस्सा नहीं है।
आरएम

15

यह C ++ में काफी बुनियादी डिजाइन निर्णय के लिए आता है।

जब कॉल को संतुष्ट करने के लिए फंक्शन की तलाश की जाती है, तो कंपाइलर इस तरह से खोज करता है:

  1. यह पहले 1 स्कोप को खोजने के लिए खोज करता है जिस पर उस नाम के साथ कुछ है

  2. कंपाइलर उस दायरे में उस नाम के साथ सभी फ़ंक्शन (या फ़ंक्शनलर्स, आदि) पाता है ।

  3. फिर संकलक उन लोगों के बीच सबसे अच्छे उम्मीदवार को खोजने के लिए प्रस्ताव को अधिभारित करता है (चाहे वे सुलभ हों या नहीं)।

  4. अंत में, कंपाइलर जांचता है कि क्या चुना गया कार्य सुलभ है।

उस आदेश के कारण, हां, यह संभव है कि संकलक एक अधिभार का चयन करेगा जो सुलभ नहीं है, भले ही एक और अधिभार हो जो सुलभ हो (लेकिन अधिभार संकल्प के दौरान नहीं चुना गया)।

जैसे कि क्या चीजों को अलग-अलग करना संभव होगा : हाँ, यह निस्संदेह संभव है। हालांकि यह निश्चित रूप से C ++ की तुलना में काफी अलग भाषा को जन्म देगा। यह पता चला है कि बहुत अधिक प्रतीत होने वाले मामूली फैसले में ऐसे प्रभाव हो सकते हैं जो बहुत अधिक प्रभावित करते हैं जो शुरू में स्पष्ट हो सकते हैं।


  1. "पहले" अपने आप में थोड़ा जटिल हो सकता है, खासकर जब / अगर टेम्पलेट शामिल हो जाते हैं, क्योंकि वे दो-चरण के लुकअप को जन्म दे सकते हैं, जिसका अर्थ है कि खोज करते समय शुरू करने के लिए दो पूरी तरह से अलग "जड़ें" हैं। हालांकि मूल विचार बहुत सरल है: सबसे छोटे से घेरने वाले दायरे से शुरू करें, और बड़े और बड़े से घेरने वाले दायरे से बाहर की ओर अपना रास्ता बनाएं।

1
स्ट्रॉस्ट्रप डी एंड ई में अटकलें लगाता है कि नियम सी के साथ इस्तेमाल किए गए प्रीप्रोसेसर का एक साइड इफेक्ट हो सकता है जो कभी भी एक बार उन्नत एडवाइजर तकनीक उपलब्ध होने पर समीक्षा नहीं हुई। मेरा जवाब देखिए
टेम्प्लेट

12

पहुँच नियंत्रण ( public, protected, private) अधिभार संकल्प को प्रभावित नहीं करते। संकलक चुनता है void foo()क्योंकि यह सबसे अच्छा मैच है। यह तथ्य कि यह सुलभ नहीं है, वह नहीं बदलता है। इसे हटाना केवल छोड़ता है void foo() const, जो तब सबसे अच्छा (यानी, केवल) मैच है।


11

इस कॉल में:

a.foo();

thisहर सदस्य फ़ंक्शन में हमेशा एक निहित सूचक उपलब्ध होता है। और की constयोग्यता thisकॉलिंग संदर्भ / ऑब्जेक्ट से ली गई है। उपरोक्त कॉल को कंपाइलर द्वारा उपचारित किया जाता है:

A::foo(a);

लेकिन तुम दो घोषणाओं के लिए है A::fooजो है की तरह व्यवहार किया :

A::foo(A* );
A::foo(A const* );

ओवरलोड रिज़ॉल्यूशन से, पहले को नॉन-कास्ट के thisलिए चुना जाएगा, दूसरे को a के लिए चुना जाएगा const this। आप पहली बार निकालते हैं, तो दूसरी दोनों के लिए बाध्य होगा constऔर non-const this

सबसे अधिक व्यवहार्य फ़ंक्शन का चयन करने के लिए अधिभार संकल्प के बाद, अभिगम नियंत्रण आता है। चूंकि आपने चुने हुए अधिभार तक पहुंच निर्दिष्ट की है private, इसलिए कंपाइलर शिकायत करेगा।

मानक ऐसा कहते हैं:

[class.access / 4] : ... अतिभारित फ़ंक्शन नामों के मामले में, लोड नियंत्रण द्वारा चयनित फ़ंक्शन पर अभिगम नियंत्रण लागू होता है ...।

लेकिन अगर आप ऐसा करते हैं:

A a;
const A& ac = a;
ac.foo();

फिर, केवल constअधिभार फिट होगा।


यह बेहतर है कि सबसे अधिक व्यवहार्य फ़ंक्शन का चयन करने के लिए अधिभार संकल्प के बाद, अभिगम नियंत्रण आता है । अभिगम नियंत्रण ओवरलोड रिज़ॉल्यूशन से पहले आना चाहिए जैसे कि यदि आपके पास पहुंच नहीं है, तो क्या आपको इस पर विचार नहीं करना चाहिए, आप क्या सोचते हैं?
नारेक

@Narek, .. मैंने अपना जवाब C ++ मानक के संदर्भ में अपडेट किया है । यह वास्तव में इस तरह से समझ में आता है, C ++ में बहुत सी चीजें और मुहावरे हैं जो इस व्यवहार पर निर्भर करते हैं
WhiZTiM

9

तकनीकी कारण अन्य उत्तरों द्वारा उत्तर दिया गया है। मैं केवल इस सवाल पर ध्यान केंद्रित करूंगा:

दूसरे शब्दों में, अभिगम नियंत्रण से पहले अधिभार संकल्प क्यों आता है? यह अजीब है। क्या आपको लगता है कि यह सुसंगत है? मेरा कोड काम करता है और फिर मैं एक विधि जोड़ता हूं और मेरा कार्य कोड बिल्कुल भी संकलित नहीं होता है।

इस तरह से भाषा को डिजाइन किया गया था। आशय यह है कि जहां तक ​​संभव हो, सर्वश्रेष्ठ व्यवहार्य अधिभार को बुलाने का प्रयास किया जाए। यदि यह विफल रहता है, तो आपको फिर से डिजाइन पर विचार करने के लिए याद दिलाने के लिए एक त्रुटि शुरू हो जाएगी।

दूसरी ओर, मान लीजिए कि आपके कोड को संकलित किया गया है और constसदस्य फ़ंक्शन को लागू करने के लिए अच्छी तरह से काम किया गया है। किसी दिन, (शायद अपने आप को) फिर से गैर- constसदस्य फ़ंक्शन की पहुँच को बदलने का निर्णय लेता privateहै public। फिर, व्यवहार किसी भी संकलित त्रुटियों के बिना बदल जाएगा! यह एक आश्चर्य होगा


8

क्योंकि फ़ंक्शन aमें चर के mainरूप में घोषित नहीं किया गया है const

निरंतर सदस्य कार्यों को निरंतर वस्तुओं पर कहा जाता है।


8

पहुँच विनिर्देशक नाम-लुकअप और फ़ंक्शन-कॉल रिज़ॉल्यूशन को कभी भी प्रभावित नहीं करते हैं। कंपाइलर जाँच से पहले फ़ंक्शन का चयन किया जाता है कि क्या कॉल को एक्सेस उल्लंघन का ट्रिगर करना चाहिए।

इस तरह, यदि आप एक एक्सेस स्पेसियर बदलते हैं, तो आपको मौजूदा समय में उल्लंघन होने पर संकलन-समय पर सतर्क कर दिया जाएगा; यदि फ़ंक्शन कॉल रिज़ॉल्यूशन के लिए गोपनीयता को ध्यान में रखा जाता है, तो आपके प्रोग्राम का व्यवहार चुपचाप बदल सकता है।

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