मैं दूसरे फ़ंक्शन के अंदर फ़ंक्शन को परिभाषित क्यों नहीं कर सकता?


80

यह एक लंबोदा फ़ंक्शन प्रश्न नहीं है, मुझे पता है कि मैं एक लंबो को एक चर में असाइन कर सकता हूं।

हमें घोषित करने की अनुमति देने की क्या बात है, लेकिन एक फ़ंक्शन को कोड के अंदर परिभाषित नहीं करना है?

उदाहरण के लिए:

#include <iostream>

int main()
{
    // This is illegal
    // int one(int bar) { return 13 + bar; }

    // This is legal, but why would I want this?
    int two(int bar);

    // This gets the job done but man it's complicated
    class three{
        int m_iBar;
    public:
        three(int bar):m_iBar(13 + bar){}
        operator int(){return m_iBar;}
    }; 

    std::cout << three(42) << '\n';
    return 0;
}

तो जो मैं जानना चाहता हूं वह यह है कि C ++ अनुमति क्यों देगा twoजो बेकार लगता है, और threeजो कहीं अधिक जटिल लगता है, लेकिन अस्वीकार करता है one?

संपादित करें:

उत्तरों से ऐसा लगता है कि कोड में घोषणा नामस्थान प्रदूषण को रोकने में सक्षम हो सकती है, मैं जो सुनने की उम्मीद कर रहा था, वह यह है कि कार्यों को घोषित करने की क्षमता की अनुमति दी गई है, लेकिन कार्यों को परिभाषित करने की क्षमता को रोक दिया गया है।


3
पहला, oneएक फ़ंक्शन परिभाषा है , अन्य दो घोषणाएं हैं
कुछ प्रोग्रामर ने 12

9
मुझे लगता है कि आपको गलत तरीके से शब्द मिल गए हैं - आप पूछना चाहते हैं "हमें घोषित करने की अनुमति देने की बात क्या है , लेकिन एक फ़ंक्शन को कोड के अंदर परिभाषित नहीं करना है ?"। और जब हम उस पर होते हैं, तो आप शायद "एक फ़ंक्शन के अंदर" का अर्थ करते हैं । यह सब "कोड" है।
पीटर -

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

4
@MikeSeymour के पास यह ठीक से है। सी, के रूप में अच्छी तरह से संरचित नहीं है, कहते हैं, पास्कल, और हमेशा केवल शीर्ष-स्तरीय फ़ंक्शन परिभाषा की अनुमति दी। इसलिए इसका कारण ऐतिहासिक है, साथ ही इसे बदलने की भी कमी है। फ़ंक्शन की घोषणाएँ संभव हैं, सामान्य रूप से संभव रूप से स्कोप की गई घोषणाओं का परिणाम है। यह निषिद्ध करना कि कार्यों के लिए एक अतिरिक्त नियम होगा।
पीटर -

3
@JonathanMee: संभवतः, क्योंकि, सामान्य तौर पर, ब्लॉक में घोषणाओं की अनुमति है, और विशेष रूप से फ़ंक्शन घोषणाओं को मना करने का कोई विशेष कारण नहीं है; किसी भी विशेष मामलों के साथ किसी भी घोषणा की अनुमति देना सरल है। लेकिन "क्यों" वास्तव में एक जवाबदेह सवाल नहीं है; भाषा यही है क्योंकि यह है कि यह कैसे विकसित हुआ है।
माइक सेमोर

जवाबों:


41

यह स्पष्ट नहीं है कि oneअनुमति क्यों नहीं है; नेस्टेड कार्यों को N0295 में बहुत पहले प्रस्तावित किया गया था जो कहता है:

हम सी ++ में नेस्टेड कार्यों की शुरूआत पर चर्चा करते हैं। नेस्टेड फ़ंक्शंस को अच्छी तरह से समझा जाता है और उनके परिचय के लिए संकलक विक्रेताओं, प्रोग्रामर, या समिति से बहुत कम प्रयास की आवश्यकता होती है। निहित कार्य महत्वपूर्ण लाभ प्रदान करते हैं, [...]

जाहिर है कि इस प्रस्ताव को अस्वीकार कर दिया गया था, लेकिन चूंकि हमारे पास बैठक के लिए मिनट उपलब्ध नहीं हैं, इसलिए हमारे पास 1993इस अस्वीकृति के लिए तर्क के लिए एक संभावित स्रोत नहीं है।

वास्तव में यह प्रस्ताव लंबोदर भावों में नोट किया गया है और संभावित विकल्प के रूप में C ++ के लिए बंद किया गया है :

एक लेख [Bre88] और C ++ कमेटी [SH93] को N0295 का प्रस्ताव देकर नेस्टेड फ़ंक्शंस को C ++ में जोड़ने का सुझाव दिया। नेस्टेड फ़ंक्शन लैम्ब्डा अभिव्यक्तियों के समान हैं, लेकिन एक फ़ंक्शन बॉडी के भीतर स्टेटमेंट्स के रूप में परिभाषित किए गए हैं, और परिणामी क्लोजर का उपयोग तब तक नहीं किया जा सकता जब तक कि फ़ंक्शन सक्रिय न हो। इन प्रस्तावों में प्रत्येक मेमने की अभिव्यक्ति के लिए एक नए प्रकार को शामिल करना शामिल नहीं है, बल्कि उन्हें सामान्य कार्यों की तरह अधिक लागू करना है, जिसमें एक विशेष प्रकार के फ़ंक्शन पॉइंटर को उनके संदर्भ में अनुमति देना शामिल है। ये दोनों प्रस्ताव C ++ के लिए टेम्पलेट्स को जोड़ने से पहले हैं, और इसलिए जेनेरिक एल्गोरिदम के संयोजन में नेस्टेड फ़ंक्शन के उपयोग का उल्लेख नहीं करते हैं। इसके अलावा, इन प्रस्तावों के पास स्थानीय चर को बंद करने के लिए कॉपी करने का कोई तरीका नहीं है, और इसलिए उनके द्वारा उत्पादित नेस्टेड फ़ंक्शन उनके संलग्न समारोह के बाहर पूरी तरह से अनुपयोगी हैं।

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

अपने प्रश्न के इस भाग के लिए:

// This is legal, but why would I want this?
int two(int bar);

ऐसे मामले हैं जहां यह आपके इच्छित फ़ंक्शन को कॉल करने का एक उपयोगी तरीका होगा। C ++ मानक खंड का मसौदा 3.4.1 [basic.lookup.unqual] हमें एक दिलचस्प उदाहरण देता है:

namespace NS {
    class T { };
    void f(T);
    void g(T, int);
}

NS::T parm;
void g(NS::T, float);

int main() {
    f(parm); // OK: calls NS::f
    extern void g(NS::T, float);
    g(parm, 1); // OK: calls g(NS::T, float)
}

1
प्रश्न ४.४.१ का उदाहरण जो आप देते हैं: मुख्य रूप ::g(parm, 1)से फोन करने वाले को ग्लोबल नेमस्पेस में फ़ंक्शन को कॉल करने के लिए नहीं लिखा ? या कॉल g(parm, 1.0f);जिसे वांछित के लिए बेहतर मैच होना चाहिए g?
पीटर -

@PeterSchneider मैंने वहां बहुत मजबूत बयान दिया, मैंने इसे समायोजित किया।
शफीक यघमौर

1
मैं यहाँ टिप्पणी जोड़ना चाहता हूँ: इस उत्तर को स्वीकार नहीं किया गया क्योंकि इसने यह समझाने का सबसे अच्छा काम किया कि कोड फ़ंक्शन घोषणाओं की अनुमति क्यों है; लेकिन क्योंकि इसने यह बताने का सबसे अच्छा काम किया कि क्यों कोड फ़ंक्शन परिभाषाओं की अनुमति नहीं है, जो कि वास्तविक प्रश्न था। और विशेष रूप से यह विशेष रूप से रेखांकित करता है कि कोड कार्यों में काल्पनिक कार्यान्वयन लैम्ब्डा के कार्यान्वयन से अलग क्यों होगा। +1
जोनाथन मी

1
@JonathanMee: दुनिया में कैसे होता है: "... हमारे पास इस अस्वीकृति के औचित्य के लिए एक संभावित स्रोत नहीं है।" नेस्टेड फंक्शन डेफिनेशन की अनुमति क्यों नहीं दी गई (या यहां तक ​​कि इसका वर्णन करने का भी प्रयास क्यों नहीं किया गया?) का सबसे अच्छा काम के रूप में अर्हता प्राप्त करें
जेरी कॉफिन

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

31

खैर, इसका जवाब "ऐतिहासिक कारण" है। सी में आप ब्लॉक स्कोप पर फ़ंक्शन घोषणाएं कर सकते हैं, और सी ++ डिजाइनरों ने उस विकल्प को हटाने में लाभ नहीं देखा।

एक उदाहरण उपयोग होगा:

#include <iostream>

int main()
{
    int func();
    func();
}

int func()
{
    std::cout << "Hello\n";
}

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


10
"यह आमतौर पर एक बुरा विचार माना जाता है '- प्रशस्ति पत्र की आवश्यकता है।
रिचर्ड हॉजेस

4
@RichardHodges: खैर, फ़ंक्शन घोषणाएँ हेडर फ़ाइलों में होती हैं, और कार्यान्वयन .c या .cpp फ़ाइलों में होती हैं, इसलिए फ़ंक्शन परिभाषाओं के अंदर इन घोषणाओं का होना उन दोनों दिशानिर्देशों में से किसी एक का उल्लंघन करता है।
एमएसलटर्स

2
यह घोषणा को परिभाषा से अलग होने से कैसे रोकता है?
रिचर्ड हॉजेस

1
@JonathanMee: मैं कह रहा हूँ कि, यदि आप जिस डिक्लेरेशन का उपयोग कर रहे हैं वह उपलब्ध नहीं है जहाँ फ़ंक्शन परिभाषित है, संकलक जाँच नहीं कर सकता है कि घोषणा परिभाषा से मेल खाती है। तो आपके पास एक स्थानीय घोषणा हो सकती है some_type f();, और दूसरी अनुवाद इकाई में परिभाषा हो सकती है another_type f() {...}। संकलक आपको यह नहीं बता सकता है कि ये मेल नहीं खाते हैं, और fगलत घोषणा के साथ कॉल करने से अपरिभाषित व्यवहार होगा। तो यह एक अच्छा विचार है कि केवल एक घोषणा है, एक हेडर में, और उस हेडर को शामिल करें जहां फ़ंक्शन परिभाषित किया गया है, साथ ही जहां इसका उपयोग किया गया है।
माइक सेमोर

6
मुझे लगता है कि आप जो कह रहे हैं वह यह है कि हेडर फ़ाइलों में फ़ंक्शन घोषणाओं को डालने का आम अभ्यास आम तौर पर उपयोगी है। मुझे नहीं लगता कि कोई भी इससे असहमत होगा। जिस कारण से मैं कोई कारण नहीं देखता, वह यह है कि फ़ंक्शन के दायरे में एक बाहरी फ़ंक्शन की घोषणा करना 'आमतौर पर एक बुरा विचार है'।
रिचर्ड होजेस

23

आपके द्वारा दिए गए उदाहरण में, void two(int)एक बाहरी फ़ंक्शन के रूप में घोषित किया जा रहा है, उस घोषणा के साथ ही mainफ़ंक्शन के दायरे में मान्य है

यह उचित है यदि आप केवल वर्तमान संकलित इकाई के भीतर वैश्विक नाम स्थान को प्रदूषित करने से बचने के लिए नाम twoउपलब्ध कराना चाहते हैं main()

टिप्पणियों के जवाब में उदाहरण:

main.cpp:

int main() {
  int foo();
  return foo();
}

foo.cpp:

int foo() {
  return 0;
}

हेडर फ़ाइलों की कोई आवश्यकता नहीं है। संकलित करें और लिंक करें

c++ main.cpp foo.cpp 

यह संकलित और चलाएगा, और उम्मीद के मुताबिक कार्यक्रम 0 पर लौटेगा।


twoफ़ाइल में भी परिभाषित नहीं किया जाएगा जिससे प्रदूषण भी हो?
जोनाथन Mee

1
@JonathanMee नहीं, two()एक पूरी तरह से अलग संकलन इकाई में परिभाषित किया जा सकता है।
रिचर्ड होजेस

मुझे यह समझने में मदद चाहिए कि यह कैसे काम करेगा। क्या आपको वह हेडर शामिल नहीं करना होगा जिसे वह घोषित किया गया था? किस बिंदु पर यह घोषित किया जाएगा, है ना? मैं अभी यह नहीं देखता कि आप इसे कोड में कैसे परिभाषित कर सकते हैं, और किसी भी तरह उस फाइल को शामिल नहीं करते हैं जो इसे घोषित करती है?
जोनाथन Mee

5
@JonathanMee हेडर के बारे में कुछ खास नहीं है। वे घोषणाएँ डालने के लिए एक सुविधाजनक जगह हैं। किसी फ़ंक्शन के भीतर एक घोषणा एक हेडर के भीतर एक घोषणा के रूप में मान्य है। तो, नहीं, आपको उस लिंक का शीर्षलेख शामिल करने की आवश्यकता नहीं होगी जिसे आप लिंक कर रहे हैं (यहां तक ​​कि एक हेडर भी नहीं हो सकता है)।
घन

1
@JonathanMee C / C ++ लिंगो में, परिभाषा और कार्यान्वयन समान हैं। आप एक फ़ंक्शन को जितनी बार चाहें उतनी बार घोषित कर सकते हैं, लेकिन आप इसे केवल एक बार परिभाषित कर सकते हैं। घोषणा को .h में समाप्त होने वाली फ़ाइल में होने की आवश्यकता नहीं है - आपके पास एक फ़ाइल का उपयोग हो सकता है ।pp जिसमें एक फ़ंक्शन पट्टी है जो फू (अपने शरीर में फू को घोषित करना) कहती है, और एक फ़ाइल प्रदान करता है। foo को परिभाषित करने वाला। और यह तब तक ठीक काम करेगा जब तक आप लिंकिंग चरण को गड़बड़ नहीं करते।
घन

19

आप इन चीजों को बड़े पैमाने पर कर सकते हैं, क्योंकि वे वास्तव में ऐसा करने में मुश्किल नहीं हैं।

संकलक के दृष्टिकोण से, किसी अन्य फ़ंक्शन के अंदर एक फ़ंक्शन घोषणा को लागू करने के लिए बहुत तुच्छ है। कंपाइलर को किसी भी फ़ंक्शन के अंदर अन्य घोषणाओं (जैसे, int x;) को संभालने के लिए फ़ंक्शन के अंदर घोषणाओं को अनुमति देने के लिए एक तंत्र की आवश्यकता होती है।

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

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

यह सवाल छोड़ देता है कि एक नेस्टेड फ़ंक्शन अलग कैसे है। एक नेस्टेड फ़ंक्शन अलग है क्योंकि यह कोड पीढ़ी को कैसे प्रभावित करता है। नेस्टेड फ़ंक्शंस की अनुमति देने वाली भाषाओं में (उदाहरण के लिए, पास्कल) आप आमतौर पर यह उम्मीद करते हैं कि नेस्टेड फ़ंक्शन में कोड फ़ंक्शन के चर तक सीधी पहुंच है जिसमें यह नेस्टेड है। उदाहरण के लिए:

int foo() { 
    int x;

    int bar() { 
        x = 1; // Should assign to the `x` defined in `foo`.
    }
}

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

int f() { 
   int x;
   int y;
   x = 1;
   y = x;
   return y;
}

एक कंपाइलर (यह मानते हुए कि यह अतिरिक्त कोड का अनुकूलन नहीं करता है) इस के बराबर कोड उत्पन्न कर सकता है:

stack_pointer -= 2 * sizeof(int);      // allocate space for local variables
x_offset = 0;
y_offset = sizeof(int);

stack_pointer[x_offset] = 1;                           // x = 1;
stack_pointer[y_offset] = stack_pointer[x_offset];     // y = x;
return_location = stack_pointer[y_offset];             // return y;
stack_pointer += 2 * sizeof(int);

विशेष रूप से, इसमें स्थानीय चर के ब्लॉक की शुरुआत की ओर इशारा करते हुए एक स्थान है, और स्थानीय चर तक सभी पहुंच उस स्थान से ऑफसेट के रूप में है।

नेस्टेड फ़ंक्शंस के साथ, अब ऐसा नहीं है - इसके बजाय, एक फ़ंक्शन की न केवल अपने स्थानीय चर तक पहुंच होती है, बल्कि उन सभी फ़ंक्शंस के लिए वैरिएबल स्थानीय तक पहुंचता है जिसमें यह नेस्टेड है। इसके बजाय केवल एक "stack_pointer" होने से जिससे यह एक ऑफसेट की गणना करता है, इसे stack_pointers को उन कार्यों के लिए स्थानीय खोजने के लिए स्टैक को फिर से चलना पड़ता है जिसमें यह नेस्टेड है।

अब, एक ऐसे तुच्छ मामले में, जो इतना भी भयानक नहीं है - अगर barअंदर घोंसला है foo, तो barबस पिछले ढेर सूचक पर ढेर को देखने के लिए fooचर तक पहुंच सकते हैं । सही?

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

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

यह जटिलता है कि सी नेस्टेड कार्यों को प्रतिबंधित करने से बच रही थी। अब, यह निश्चित रूप से सच है कि एक वर्तमान सी ++ कंपाइलर 1970 के विंटेज सी कंपाइलर से एक अलग तरह का जानवर है। मल्टीपल, वर्चुअल इनहेरिटेंस जैसी चीजों के साथ, C ++ कंपाइलर को किसी भी स्थिति में इसी सामान्य प्रकृति की चीजों से निपटना पड़ता है (यानी, ऐसे मामलों में बेस-क्लास वेरिएबल का स्थान ढूंढना गैर-तुच्छ भी हो सकता है)। प्रतिशत के आधार पर, नेस्टेड फ़ंक्शंस का समर्थन करना वर्तमान C ++ कंपाइलर (और कुछ, जैसे कि जीसीसी, पहले से ही उनका समर्थन करता है) में बहुत जटिलता नहीं जोड़ेगा।

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

निचला रेखा: भले ही यह शुरू में ऐसा लगे कि नेस्टेड घोषणाएँ कठिन हैं और नेस्टेड कार्य तुच्छ हैं, कमोबेश यह विपरीत सच है: नेस्टेड फ़ंक्शन वास्तव में नेस्टेड घोषणाओं की तुलना में समर्थन के लिए बहुत अधिक जटिल हैं।


5

पहला एक फ़ंक्शन परिभाषा है, और इसकी अनुमति नहीं है। जाहिर है, wt किसी फ़ंक्शन की परिभाषा को दूसरे फ़ंक्शन के अंदर डालने का उपयोग है।

लेकिन अन्य दो ही घोषणाएं हैं। कल्पना करें कि आपको int two(int bar);मुख्य विधि के अंदर फ़ंक्शन का उपयोग करने की आवश्यकता है । लेकिन यह नीचे परिभाषित किया गया हैmain() फ़ंक्शन के है, ताकि फ़ंक्शन के अंदर फ़ंक्शन की घोषणा आपको घोषणाओं के साथ उस फ़ंक्शन का उपयोग करने के लिए बनाती है।

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

int main()
{
    // This is legal, but why would I want this?
    int two(int bar);

    //Call two
    int x = two(7);

    class three {
        int m_iBar;
        public:
            three(int bar):m_iBar(13 + bar) {}
            operator int() {return m_iBar;}
    };

    //Use class
    three *threeObj = new three();

    return 0;
}

2
"मंदी" क्या है? क्या आपका मतलब "घोषणा" है?
पीटर मोर्टेंसन

4

यह भाषा सुविधा सी से विरासत में मिली थी, जहां इसने सी के शुरुआती दिनों में कुछ उद्देश्य पूरा किया (फंक्शन डिक्लेरेशन स्कूपिंग? हो सकता है?) । मुझे नहीं पता कि यह सुविधा आधुनिक सी प्रोग्रामर द्वारा उपयोग की जाती है या नहीं और मुझे पूरी तरह से संदेह है।

तो, जवाब का योग करने के लिए:

आधुनिक C ++ में इस सुविधा का कोई उद्देश्य नहीं है (जो मुझे पता है, कम से कम), यह यहाँ C ++ - to-C पिछड़े संगतता (I suppose :)) के कारण है।


नीचे टिप्पणी के लिए धन्यवाद:

फ़ंक्शन प्रोटोटाइप को उस फ़ंक्शन के लिए स्कोप किया जाता है, जिसमें इसे घोषित किया जाता है, इसलिए किसी के पास बाहरी कार्यों / प्रतीकों का उल्लेख करके - एक टियरियर ग्लोबल नाम स्थान हो सकता है #include


उद्देश्य वैश्विक नेमस्पेस प्रदूषण से बचने के लिए नाम के दायरे को नियंत्रित कर रहा है।
रिचर्ड हॉजेस

ठीक है, मुझे लगता है कि यह स्थितियों के लिए उपयोगी है, जब आप #include के साथ वैश्विक नाम स्थान को प्रदूषित किए बिना बाहरी कार्यों / प्रतीकों का उल्लेख करना चाहते हैं! इस पर ध्यान दिलाने के लिए धन्यवाद। मैं एक संपादन करूँगा।
mr.pd

4

असल में, एक उपयोग मामला है जो गर्भ धारण करने योग्य है। यदि आप यह सुनिश्चित करना चाहते हैं कि एक निश्चित फ़ंक्शन कहा जाता है (और आपका कोड संकलित करता है), कोई फर्क नहीं पड़ता कि आसपास का कोड क्या घोषित करता है, तो आप अपना स्वयं का ब्लॉक खोल सकते हैं और इसमें फ़ंक्शन प्रोटोटाइप की घोषणा कर सकते हैं। (प्रेरणा मूल रूप से जोहान्स स्काउब, https://stackoverflow.com/a/929902/3150802 , TeKa, https://stackoverflow.com/a/8821992/3150802 के माध्यम से है )।

यह विशेष रूप से उपयोगी हो सकता है यदि आपको हेडर को शामिल करना है जिसे आप नियंत्रित नहीं करते हैं, या यदि आपके पास एक मल्टी-लाइन मैक्रो है जो अज्ञात कोड में उपयोग किया जा सकता है।

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

// somebody's header
void f();

// your code
{   int i;
    int f(); // your different f()!
    i = f();
    // ...
}

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


तो यहां मेरी मदद करें, fआपके उदाहरण में कहां परिभाषित होगा ? क्या मैं फ़ंक्शन पुनर्निर्धारण त्रुटि के साथ समाप्त नहीं करूंगा क्योंकि ये केवल रिटर्न प्रकार से भिन्न हैं?
जोनाथन मी

@JonathanMee hmmm ... f () को एक अलग अनुवाद इकाई में परिभाषित किया जा सकता है, मैंने सोचा। लेकिन शायद लिंकर को गंजा कर दिया जाएगा यदि आप मान्यता प्राप्त पुस्तकालय के खिलाफ भी जुड़े, मुझे लगता है, आप सही हैं। तो आप ऐसा नहीं कर सकते ;-), या कम से कम कई परिभाषाओं को अनदेखा करना होगा।
पीटर -

खराब उदाहरण। C ++ में कोई अंतर नहीं है void f()और int f()क्योंकि फ़ंक्शन का रिटर्न मान C ++ में फ़ंक्शन के हस्ताक्षर का हिस्सा नहीं है। दूसरी घोषणा को बदलें int f(int)और मैं अपना पद छोड़ दूंगा।
डेविड हैम्मन

@David Hammen i = f();घोषणा करने के बाद संकलन करने का प्रयास करें void f()। "कोई भेद नहीं" केवल आधा सच है; ;-) मैंने वास्तव में गैर-अधिभार समारोह "हस्ताक्षरों" का उपयोग किया क्योंकि अन्यथा पूरी परिस्थिति सी ++ में अनावश्यक होगी क्योंकि विभिन्न पैरामीटर प्रकार / संख्या वाले दो कार्य खुशी से सह-अस्तित्व में ला सकते हैं।
पीटर -

@ दाविदहमेन वास्तव में, शफीक के जवाब को पढ़ने के बाद मेरा मानना ​​है कि हमारे पास तीन मामले हैं: 1. हस्ताक्षर मापदंडों में भिन्न हैं। C ++ में कोई समस्या नहीं, सरल अधिभार और सर्वश्रेष्ठ मिलान नियम काम करते हैं। 2. हस्ताक्षर बिल्कुल अलग नहीं है। भाषा के स्तर पर कोई मुद्दा नहीं; वांछित कार्यान्वयन के खिलाफ लिंक करके फ़ंक्शन को हल किया जाता है। 3. अंतर केवल रिटर्न प्रकार में है। वहाँ है भाषा के स्तर पर एक मुद्दा है, के रूप में प्रदर्शन किया; अधिभार संकल्प काम नहीं करता है; हमें एक अलग हस्ताक्षर और उचित रूप से लिंक के साथ एक फ़ंक्शन घोषित करना होगा ।
पीटर -

3

यह ओपी सवाल का जवाब नहीं है, बल्कि कई टिप्पणियों का जवाब है।

मैं टिप्पणियों और उत्तरों में इन बिंदुओं से असहमत हूं: 1 कि नेस्टेड घोषणाएं कथित रूप से हानिरहित हैं, और 2 जो नेस्टेड परिभाषाएं बेकार हैं।

1 नेस्टेड फ़ंक्शन घोषणाओं की कथित हानिरहितता के लिए प्रमुख प्रतिसाद कुख्यात मोस्ट वैक्सिंग पार्स है । IMO के कारण भ्रम की स्थिति फैलती है, नेस्टेड घोषणाओं को मना करने के लिए एक अतिरिक्त नियम को वारंट करने के लिए पर्याप्त है।

2 नेस्टेड फ़ंक्शन परिभाषाओं की कथित बेकारता के लिए पहली बार प्रतिसाद अक्सर एक ही फ़ंक्शन के अंदर कई स्थानों पर एक ही ऑपरेशन करने की आवश्यकता होती है। इसके लिए एक स्पष्ट समाधान है:

private:
inline void bar(int abc)
{
    // Do the repeating operation
}

public: 
void foo()
{
    int a, b, c;
    bar(a);
    bar(b);
    bar(c);
}

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


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

2

विशेष रूप से इस सवाल का जवाब:

उत्तरों से ऐसा लगता है कि कोड में घोषणा नामस्थान प्रदूषण को रोकने में सक्षम हो सकती है, मैं जो सुनने की उम्मीद कर रहा था, वह यह है कि कार्यों को घोषित करने की क्षमता की अनुमति दी गई है, लेकिन कार्यों को परिभाषित करने की क्षमता को रोक दिया गया है।

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

int main()
{
  int foo() {

    // Do something
    return 0;
  }
  return 0;
}

भाषा डिजाइनरों के लिए प्रश्न:

  1. चाहिए foo()अन्य कार्यों के लिए उपलब्ध चाहिए?
  2. यदि हां, तो इसका नाम क्या होना चाहिए? int main(void)::foo()?
  3. (ध्यान दें कि 2 C में संभव नहीं होगा, C ++ के प्रवर्तक)
  4. यदि हम एक स्थानीय फ़ंक्शन चाहते हैं, तो हमारे पास पहले से ही एक तरीका है - इसे स्थानीय स्तर पर परिभाषित वर्ग का एक स्थिर सदस्य बनाएं। तो क्या हमें उसी परिणाम को प्राप्त करने के लिए एक और वाक्यविन्यास विधि जोड़ना चाहिए? ऐसा क्यों करते हैं? क्या यह C ++ कंपाइलर डेवलपर्स के रखरखाव के बोझ को नहीं बढ़ाएगा?
  5. और इसी तरह...

जाहिर है यह व्यवहार लंबोदर के लिए परिभाषित है? क्यों नहीं कोड में परिभाषित कार्य करता है?
जोनाथन मी

फंक्शन ऑब्जेक्ट लिखने के लिए एक लैम्ब्डा महज शॉर्टहैंड है। लैंबडा का विशेष मामला जो कोई तर्क नहीं देता है, एक स्थानीय फ़ंक्शन परिभाषा के बराबर है, जैसा कि एक फ़ंक्शन ऑब्जेक्ट लिख रहा है जिसमें कोई डेटा सदस्य नहीं है।
रिचर्ड होजेस

मैं सिर्फ उस लंबोदर को इंगित कर रहा था, और कोड में घोषित कार्यों ने पहले ही आपके सभी बिंदुओं को खारिज कर दिया था। यह "बोझ" में कोई वृद्धि नहीं होनी चाहिए।
जोनाथन मी

@JonathanMee यदि आप इसके बारे में दृढ़ता से महसूस करते हैं, तो हर तरह से c ++ मानक समिति को एक RFC प्रस्तुत करें।
रिचर्ड हॉजेस

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

1

बस यह इंगित करना चाहता था कि जीसीसी संकलक आपको फ़ंक्शन के अंदर फ़ंक्शन घोषित करने की अनुमति देता है। इसके बारे में यहाँ और पढ़ें । साथ ही लैम्बदास के C ++ में आने से, यह प्रश्न अब थोड़ा अप्रचलित है।


अन्य कार्यों के अंदर फ़ंक्शन हेडर घोषित करने की क्षमता, मुझे निम्नलिखित मामले में उपयोगी मिली:

void do_something(int&);

int main() {
    int my_number = 10 * 10 * 10;
    do_something(my_number);

    return 0;
}

void do_something(int& num) {
    void do_something_helper(int&); // declare helper here
    do_something_helper(num);

    // Do something else
}

void do_something_helper(int& num) {
    num += std::abs(num - 1337);
}

यहाँ क्या हो रहआ हैं? मूल रूप से, आपके पास एक फ़ंक्शन है जिसे मुख्य से बुलाया जाना है, इसलिए आप क्या करते हैं कि आप इसे सामान्य की तरह घोषित करते हैं। लेकिन तब आपको पता चलता है, कि इस कार्य को करने के लिए एक और फ़ंक्शन की आवश्यकता है। इसलिए उस सहायक फ़ंक्शन को मुख्य रूप से घोषित करने के बजाय, आप इसे उस फ़ंक्शन के अंदर घोषित करते हैं, जिसे इसकी आवश्यकता होती है और फिर इसे उस फ़ंक्शन और उस फ़ंक्शन से ही कॉल किया जा सकता है।

मेरी बात यह है कि, फ़ंक्शन के अंदर फ़ंक्शन हेडर घोषित करना फ़ंक्शन इनकैप्सुलेशन का एक अप्रत्यक्ष तरीका हो सकता है, जो किसी फ़ंक्शन को कुछ अन्य फ़ंक्शन को सौंपने से कुछ हिस्सों को छिपाने की अनुमति देता है जो केवल इसके बारे में जानते हैं, लगभग एक नेस्टेड का भ्रम दे रहा है कार्य


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

0

नेस्टेड फ़ंक्शन घोषणाओं को संभवतः 1 के लिए अनुमति दी जाती है। अग्रेषित संदर्भ 2. कार्य करने के लिए एक पॉइंटर घोषित करने में सक्षम होने के लिए और एक सीमित दायरे में अन्य फ़ंक्शन (एस) के आसपास से गुजरने के लिए।

नेस्टेड फ़ंक्शन परिभाषाओं को संभवतः 1. ऑप्टिमाइज़ेशन 2 जैसे मुद्दों के कारण अनुमति नहीं दी जाती है। रिकर्सन (एनक्लोजिंग और नेस्टेड डिफाइन्ड फ़ंक्शन (एस)) 3. री-एंट्रेंस 4. कंसीरेंसी और अन्य मल्टीथ्रेड एक्सेस मुद्दे।

मेरी सीमित समझ से :)

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