स्थिर डेटा सदस्यों को C ++ (जावा के विपरीत) में अलग से कक्षा के बाहर परिभाषित क्यों किया जाना है?


41
class A {
  static int foo () {} // ok
  static int x; // <--- needed to be defined separately in .cpp file
};

मुझे A::xएक .cpp फ़ाइल (या टेम्प्लेट के लिए एक ही फ़ाइल) में अलग से परिभाषित होने की आवश्यकता नहीं है । A::xएक ही समय में घोषित और परिभाषित क्यों नहीं किया जा सकता है ?

क्या ऐतिहासिक कारणों से इसे मना किया गया है?

मेरा मुख्य सवाल यह है कि क्या staticडेटा सदस्यों को उसी समय ( जावा के समान ) घोषित किया गया है, तो क्या यह किसी कार्यक्षमता को प्रभावित करेगा ?


एक सर्वोत्तम अभ्यास के रूप में, प्रारंभिक क्रम की समस्याओं से बचने के लिए आमतौर पर अपने स्थिर चर को स्थिर विधि (संभवतः स्थानीय स्थैतिक के रूप में) से लपेटना बेहतर होता है।
तमसे सजेलेई

2
यह नियम वास्तव में C ++ 11 में थोड़ा आराम दिया गया है। स्थैतिक सदस्यों को आमतौर पर अब परिभाषित करने की आवश्यकता नहीं है। देखें: en.wikipedia.org/wiki/…
mirk

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

1
ढेर अतिप्रवाह पर विश्वास करता है?
मोनिका

2
C ++ 17 स्थैतिक डेटा सदस्यों के इनलाइन आरंभीकरण की अनुमति देता है (यहां तक ​​कि गैर-पूर्णांक प्रकारों के लिए) inline static int x[] = {1, 2, 3};:। En.cppreference.com/w/cpp/language/static#Static_data_members
व्लादिमीर

जवाबों:


15

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

C ++ संकलन मॉडल C के उस से उपजा है, जिसमें आप (हेडर) फ़ाइलों को स्रोत फ़ाइल में घोषणाएँ आयात करते हैं। इस तरह, संकलक बिल्कुल एक बड़ी स्रोत फ़ाइल को देखता है, जिसमें सभी सम्मिलित फाइलें होती हैं, और उन फ़ाइलों से शामिल सभी फाइलें पुनरावर्ती होती हैं। इसका IMO एक बड़ा लाभ है, अर्थात् यह संकलक को लागू करने में आसान बनाता है। बेशक, आप शामिल फ़ाइलों में कुछ भी लिख सकते हैं, अर्थात घोषणा और परिभाषा दोनों। हेडर फ़ाइलों और परिभाषाओं में .c या .cpp फ़ाइलों में घोषणाएँ डालना एक अच्छा अभ्यास है।

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

उदाहरण के लिए, GNU पास्कल में आप इस तरह से aएक फ़ाइल में एक इकाई लिख सकते हैं a.pas:

unit a;

interface

var MyStaticVariable: Integer;

implementation

begin
  MyStaticVariable := 0
end.

जहां वैश्विक चर घोषित किया जाता है और उसी स्रोत फ़ाइल में प्रारंभ किया जाता है।

तब आपके पास अलग-अलग इकाइयाँ हो सकती हैं जो वैश्विक चर का उपयोग और आयात करती हैं MyStaticVariable, जैसे एक इकाई b ( b.pas):

unit b;

interface

uses a;

procedure PrintB;

implementation

procedure PrintB;
begin
  Inc(MyStaticVariable);
  WriteLn(MyStaticVariable)
end;
end.

और एक इकाई c ( c.pas):

unit c;

interface

uses a;

procedure PrintC;

implementation

procedure PrintC;
begin
  Inc(MyStaticVariable);
  WriteLn(MyStaticVariable)
end;
end.

अंत में आप एक मुख्य कार्यक्रम में इकाइयों बी और सी का उपयोग कर सकते हैं m.pas:

program M;

uses b, c;

begin
  PrintB;
  PrintC;
  PrintB
end.

आप इन फ़ाइलों को अलग से संकलित कर सकते हैं:

$ gpc -c a.pas
$ gpc -c b.pas
$ gpc -c c.pas
$ gpc -c m.pas

और उसके बाद एक निष्पादन योग्य उत्पादन करें:

$ gpc -o m m.o a.o b.o c.o

और इसे चलाएं:

$ ./m
1
2
3

चाल यहाँ है कि जब संकलक देखता है एक है का उपयोग करता है एक कार्यक्रम मॉड्यूल में निर्देशक (जैसे b.pas में एक का उपयोग करता है), यह इसी .pas फ़ाइल शामिल नहीं है, लेकिन एक .gpi फ़ाइल, एक पूर्व संकलित के लिए यानी के लिए दिखता है इंटरफ़ेस फ़ाइल ( प्रलेखन देखें )। इन .gpiफ़ाइलों को कंपाइलर द्वारा .oफ़ाइलों के साथ मिलकर उत्पन्न किया जाता है जब प्रत्येक मॉड्यूल संकलित किया जाता है। तो वैश्विक प्रतीक MyStaticVariableकेवल ऑब्जेक्ट फ़ाइल में एक बार परिभाषित किया गया है a.o

जावा एक समान तरीके से काम करता है: जब कंपाइलर वर्ग A को वर्ग B में आयात करता है, तो यह A के लिए वर्ग फ़ाइल को देखता है और उसे फ़ाइल की आवश्यकता नहीं होती है A.java। इसलिए कक्षा ए के लिए सभी परिभाषाएँ और इनिशियलाइज़ेशन को एक स्रोत फ़ाइल में रखा जा सकता है।

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


42

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

यदि आप उन्हें हेडर में परिभाषित करने का प्रयास करते हैं, तो उन्हें हर मॉड्यूल में परिभाषित किया जाएगा जिसमें उस हेडर शामिल हैं, और आपको लिंक करने के दौरान त्रुटियां मिलेंगी क्योंकि यह सभी डुप्लिकेट परिभाषाओं का पता लगाता है।

हां, यह कम से कम आंशिक रूप से एक ऐतिहासिक मुद्दा है, जो कि सिफर से डेटिंग है; एक संकलक लिखा जा सकता है जो एक प्रकार का छिपा हुआ "static_members_of_everything.cpp" और उसी से लिंक करेगा। हालांकि, यह पश्चगामी संगतता को तोड़ देगा, और ऐसा करने का कोई वास्तविक लाभ नहीं होगा।


2
मेरा प्रश्न वर्तमान व्यवहार का कारण नहीं है, बल्कि ऐसी भाषा व्याकरण का औचित्य है। दूसरे शब्दों में, मान लीजिए कि यदि staticचर (उसी तरह जावा) पर घोषित / परिभाषित किए जाते हैं तो क्या गलत हो सकता है?
इमिलिंद

8
@iammilind मुझे लगता है कि आप यह नहीं समझते हैं कि इस उत्तर की व्याख्या के कारण व्याकरण आवश्यक है। अब क्यों? C (और C ++) के संकलन मॉडल के कारण: c और cpp फाइलें वास्तविक कोड फ़ाइल हैं, जिन्हें अलग-अलग कार्यक्रमों की तरह अलग-अलग संकलित किया जाता है, फिर उन्हें पूर्ण निष्पादन योग्य बनाने के लिए एक साथ जोड़ा जाता है। हेडर वास्तव में संकलक के लिए कोड नहीं हैं, वे केवल c और cpp फ़ाइलों के अंदर कॉपी और पेस्ट करने के लिए पाठ हैं। अब यदि किसी चीज को कई बार परिभाषित किया जाता है, तो वह उसे संकलित नहीं कर सकती है, उसी तरह यदि आप एक ही नाम के साथ कई स्थानीय चर संकलित नहीं करेंगे।
KLL

1
@ पता, staticसदस्यों के बारे में क्या template? सभी हेडर फ़ाइलों में उन्हें अनुमति दी जाती है क्योंकि उन्हें दिखाई देने की आवश्यकता होती है। मैं इस जवाब पर विवाद नहीं कर रहा हूं, लेकिन यह मेरे सवाल से भी मेल नहीं खाता है।
इमिलिंड

@iammilind टेम्पलेट वास्तविक कोड नहीं हैं, वे कोड हैं जो कोड उत्पन्न करते हैं। टेम्पलेट के प्रत्येक उदाहरण में संकलक द्वारा प्रदान की जाने वाली प्रत्येक स्थिर घोषणा का एक और केवल एक स्थिर उदाहरण होता है। आपको अभी भी उदाहरण को परिभाषित करना है लेकिन जैसा कि आप एक उदाहरण के टेम्पलेट को परिभाषित करते हैं, यह वास्तविक कोड नहीं है, जैसा कि ऊपर कहा गया है। टेम्पलेट्स, शाब्दिक, कोड उत्पन्न करने के लिए संकलक के लिए कोड के टेम्पलेट हैं।
KLL

2
@iammilind: टेम्प्लेट आमतौर पर हर स्टैटिक फ़ाइल में उनके स्टैटिक वेरिएबल्स सहित तुरंत तैयार किए जाते हैं। ईएलएफ ऑब्जेक्ट फ़ाइलों के साथ लिनक्स पर, कंपाइलर तात्कालिकता को कमजोर प्रतीकों के रूप में चिह्नित करता है , जिसका अर्थ है कि लिंकर समान तात्कालिकता की कई प्रतियों को जोड़ता है। हेडर फ़ाइलों में स्थिर चर को परिभाषित करने की अनुमति देने के लिए उसी तकनीक का उपयोग किया जा सकता है, इसलिए ऐसा नहीं होने का कारण संभवतः ऐतिहासिक कारणों और संकलन प्रदर्शन के विचारों का संयोजन है। अगले C ++ मानक में मॉड्यूल शामिल होते ही पूरा संकलन मॉडल उम्मीद से तय हो जाएगा ।
हन

6

इसका संभावित कारण यह है कि यह C ++ भाषा को उन वातावरणों में लागू करने योग्य रखता है जहां ऑब्जेक्ट फ़ाइल और लिंकेज मॉडल कई ऑब्जेक्ट फ़ाइलों से कई परिभाषाओं के विलय का समर्थन नहीं करता है।

एक वर्ग घोषणा (अच्छे कारणों के लिए एक घोषणा कहा जाता है) कई अनुवाद इकाइयों में खींच ली जाती है। यदि घोषणा में स्थिर चर के लिए परिभाषाएं होती हैं, तो आप कई अनुवाद इकाइयों में कई परिभाषाओं के साथ समाप्त हो जाएंगे (और याद रखें, इन नामों में बाहरी संबंध हैं।)

वह स्थिति संभव है, लेकिन शिकायतकर्ता को शिकायत के बिना कई परिभाषाओं को संभालने की आवश्यकता होती है।

(और ध्यान दें कि यह एक परिभाषा नियम के साथ संघर्ष करता है, जब तक कि यह एक प्रतीक के प्रकार के अनुसार नहीं किया जा सकता है या इसे किस प्रकार के अनुभाग में रखा गया है)


6

C ++ और Java में बहुत अंतर है।

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

C ++ में कोई "अंतिम ज्ञान स्वामी" नहीं है: C ++, C, फोरट्रान पास्कल आदि सभी एक स्रोत कोड (CPP फ़ाइल) से एक मध्यवर्ती प्रारूप (OBJ फ़ाइल, या ".o") फ़ाइल में "अनुवादक" हैं, जो निर्भर करता है। OS) जहां स्टेटमेंट को मशीन इंस्ट्रक्शन में अनुवादित किया जाता है और नाम अप्रत्यक्ष पते एक प्रतीक तालिका द्वारा मध्यस्थता बन जाते हैं।

एक प्रोग्राम कंपाइलर द्वारा नहीं बनाया जाता है, लेकिन एक अन्य प्रोग्राम ("लिंकर") द्वारा, जो सभी OBJ-s को एक साथ जोड़ देता है (कोई फर्क नहीं पड़ता कि वे जिस भाषा से आते हैं) उन सभी पतों को फिर से इंगित करके, जो प्रतीकों की ओर हैं, उनकी ओर प्रभावी परिभाषा।

जिस तरह से लिंकर काम करता है, एक परिभाषा (एक चर के लिए भौतिक स्थान बनाता है) अद्वितीय होना चाहिए।

ध्यान दें कि C ++ स्वयं लिंक नहीं करता है, और यह कि लिंकर C ++ स्पेक्स द्वारा जारी नहीं किया जाता है: OS मॉड्यूल के निर्माण के तरीके से लिंकर मौजूद है (आमतौर पर C और ASM में)। C ++ को इसे जिस तरह से उपयोग करना है।

अब: एक हेडर फाइल कुछ "सीपीपी फाइल" में चिपका दी जाती है। प्रत्येक CPP फ़ाइल का हर दूसरे स्वतंत्र रूप से अनुवाद किया जाता है। विभिन्न CPP फ़ाइलों का अनुवाद करने वाला एक कंपाइलर, सभी प्राप्त- समान परिभाषा में सभी परिणामी OBJ में परिभाषित ऑब्जेक्ट के लिए " निर्माण कोड " होगा।

कंपाइलर को पता नहीं है (और कभी पता नहीं चलेगा) यदि उन सभी ओबीजे का उपयोग कभी भी एक साथ करने के लिए या अलग-अलग स्वतंत्र प्रोग्राम बनाने के लिए किया जाएगा।

लिंक करने वाले को यह पता नहीं होता है कि परिभाषाएँ कैसे और क्यों मौजूद हैं और वे कहाँ से आते हैं (यह C ++ के बारे में भी नहीं जानता: प्रत्येक "स्थिर भाषा" लिंक करने के लिए परिभाषाएँ और संदर्भ उत्पन्न कर सकती है)। यह सिर्फ जानता है कि किसी दिए गए "प्रतीक" के संदर्भ हैं जो दिए गए पते पर "परिभाषित" हैं।

यदि किसी दिए गए प्रतीक के लिए कई परिभाषाएं (संदर्भों के साथ परिभाषाओं को भ्रमित न करें) हैं, तो उनके साथ क्या करना है, इसके बारे में लिंक करने वाले को कोई ज्ञान (भाषा अज्ञेयवादी नहीं) है।

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


3
वैश्विक प्रतीकों के संबंध में जावा और सी ++ के बीच का अंतर जावा के साथ वर्चुअल मशीन से जुड़ा नहीं है, बल्कि सी ++ संकलन मॉडल के साथ है। इस संबंध में, मैं पास्कल और सी ++ को एक ही श्रेणी में नहीं रखूंगा। बल्कि मैं C और C ++ को "उन भाषाओं के रूप में समूहित करूंगा, जिनमें जावा और पास्कल (और शायद OCaml, Scala, Ada, आदि) के विपरीत" मुख्य भाषा के रूप में आयातित घोषणाएं शामिल हैं और एक साथ संकलित की गई हैं " आयातित घोषणाओं को संकलित द्वारा पूर्व संकलित फाइलों में देखा जाता है, जिसमें निर्यातित प्रतीकों के बारे में जानकारी होती है ”।
जियोर्जियो

1
@ जियोर्जियो: जावा के संदर्भ का स्वागत नहीं किया जा सकता है, लेकिन मुझे लगता है कि एमिलियो का जवाब ज्यादातर संकलन के बाद वस्तु फ़ाइल / लिंकर चरण के मुद्दे पर मिलने से ठीक है।
ixache

5

इसकी आवश्यकता है क्योंकि अन्यथा संकलक को यह नहीं पता है कि चर को कहां रखा जाए। प्रत्येक cpp फ़ाइल व्यक्तिगत रूप से संकलित है और दूसरे के बारे में नहीं जानती है। लिंकर वैरिएबल, फ़ंक्शंस, आदि को हल करता है। मैं व्यक्तिगत रूप से यह नहीं देखता कि अंतर और स्थिर सदस्यों के बीच क्या अंतर है (हमें यह चुनने की ज़रूरत नहीं है कि कौन सी फ़ाइल vtable में परिभाषित की गई है)।

मैं ज्यादातर मानता हूं कि कंपाइलर लेखकों के लिए इसे इस तरह से लागू करना आसान है। वर्ग / संरचना के बाहर स्थैतिक संस्करण मौजूद हैं और शायद या तो स्थिरता के कारणों के लिए या क्योंकि यह संकलक लेखकों के लिए 'लागू करना आसान' होगा, उन्होंने मानकों में उस प्रतिबंध को परिभाषित किया था।


2

मुझे लगता है कि मुझे इसका कारण मिला। staticअलग-अलग स्थान में चर को परिभाषित करने से इसे किसी भी मूल्य पर आरंभ करने की अनुमति मिलती है। यदि आरंभिक नहीं है तो यह 0 पर डिफ़ॉल्ट हो जाएगा।

C ++ 11 से पहले C ++ में क्लास इनिशियलाइज़ेशन की अनुमति नहीं थी। इसलिए कोई ऐसा नहीं लिख सकता :

struct X
{
  static int i = 4;
};

इस प्रकार अब वैरिएबल को इनिशियलाइज़ करने के लिए इसे कक्षा के बाहर लिखना होगा:

struct X
{
  static int i;
};
int X::i = 4;

जैसा कि अन्य उत्तरों में भी चर्चा की गई है, int X::iअब एक वैश्विक है और कई फाइलों में वैश्विक घोषित करना कई प्रतीक लिंक त्रुटि का कारण बनता है।

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

static int X::i = 4;
^^^^^^

0

ए :: एक्स सिर्फ एक वैश्विक परिवर्तनशील है, लेकिन ए और एक्सेस प्रतिबंधों के साथ नाम स्थान है।

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

मैं इन सभी खराब डिज़ाइन को कॉल करूंगा, लेकिन कुछ विशेषताएं हैं जो आप इस तरह से शोषण कर सकते हैं:

  1. कंस्ट्रक्टर कॉल ऑर्डर ... एक इंट के लिए महत्वपूर्ण नहीं है, लेकिन एक अधिक जटिल सदस्य के लिए जो शायद अन्य स्थिर या वैश्विक चर तक पहुंचता है, यह महत्वपूर्ण हो सकता है।

  2. स्थिर इनिशियलाइज़र - आप एक ग्राहक को यह तय करने दे सकते हैं कि A :: x को इनिशियलाइज़ किया जाना चाहिए।

  3. सी ++ और सी में, क्योंकि आपके पास पॉइंटर्स के माध्यम से मेमोरी तक पूर्ण पहुंच है, चर का भौतिक स्थान महत्वपूर्ण है। बहुत ही शरारती चीजें हैं जो आप एक लिंक ऑब्जेक्ट में एक चर स्थित है के आधार पर शोषण कर सकते हैं।

मुझे संदेह है कि ये "क्यों" यह स्थिति पैदा हुई है। यह शायद सिर्फ C ++ में C का विकास है, और एक पश्चगामी संगतता समस्या है जो आपको अब भाषा बदलने से रोकती है।


2
ऐसा लगता नहीं है कि बनाए गए कुछ बिंदुओं पर पर्याप्त रूप से कुछ भी नहीं दिया गया है और पहले 6 जवाबों में समझाया गया है
gnat
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.