हेडर में extern के साथ ग्लोबल घोषित करें, उसे ठीक एक .cpp फ़ाइल में परिभाषित करें, और उस फ़ाइल के ऑब्जेक्ट को हर कॉलर के साथ लिंक करें। यह गाइड macOS पर पूरा बिल्ड, एड्रेस जाँच, और यह बताती है कि C++17 का inline वेरिएबल कब उपयुक्त है।

हेडर में ग्लोबल रखने से डुप्लिकेट परिभाषाएँ क्यों बनती हैं?

एक छोटे नेटिव Mac प्रोग्राम में कई अलग-अलग संकलित सोर्स फ़ाइलों को एक ही ऑब्जेक्ट पढ़ने और संशोधित करने देना होता है। घोषणा और परिभाषा को अलग करके शुरू करें: extern int itemCount; किसी ऑब्जेक्ट की घोषणा करता है, जबकि int itemCount = 0; उसे परिभाषित करता है। extern में इनिशियलाइज़र जोड़ना, जैसे extern int itemCount = 0;, उसे भी परिभाषा बना देता है। C++ वर्किंग ड्राफ़्ट में यह अंतर घोषणाएँ और परिभाषाएँ में दर्ज है।

साधारण नेमस्पेस-स्कोप परिभाषा वाला हेडर उस परिभाषा को शामिल करने वाली हर सोर्स फ़ाइल में पहुँचा सकता है। बाहरी लिंकेज वाले नॉन-इनलाइन, नॉन-टेम्पलेट वेरिएबल की कई परिभाषाएँ अलग-अलग ट्रांसलेशन यूनिट में होना one-definition rule का उल्लंघन है। लिंकर डुप्लिकेट सिंबल की रिपोर्ट कर सकता है, लेकिन भाषा यह आवश्यक नहीं करती कि हर ऐसे उल्लंघन का निदान किया जाए। वर्किंग ड्राफ़्ट का One-definition rule देखें।

इन्क्लूड गार्ड एक ही ट्रांसलेशन यूनिट के भीतर बार-बार शामिल होने से रोकता है। यह अलग-अलग संकलित सोर्स फ़ाइलों के बीच तालमेल नहीं करता। इसलिए गार्ड वाला हेडर भी ऑब्जेक्ट फ़ाइलों में कई परिभाषाएँ पैदा कर सकता है। Clang की कंपाइलर कमांड गाइड प्रीप्रोसेसिंग के दौरान हेडर विस्तार और बाद में ऑब्जेक्ट फ़ाइलों को लिंक करने का वर्णन करती है।

अपने Mac पर साझा ग्लोबल का पूरा उदाहरण बनाएँ

Xcode या Apple के Xcode पैकेज के Command Line Tools में से किसी एक का उपयोग करें। Apple स्टैंडअलोन पैकेज को Xcode के बाहर डेवलपमेंट के विकल्प के रूप में दर्ज करता है; पूरे Xcode इंस्टॉलेशन में ये टूल पहले से शामिल होते हैं। अगर इनमें से कुछ भी इंस्टॉल नहीं है, तो नीचे दिया कमांड चलाएँ और इंस्टॉलेशन डायलॉग पूरा करें। कमांड-लाइन टूल इंस्टॉल करना देखें।

xcode-select --install

बिल्ड करने से पहले चुनी हुई डेवलपर डायरेक्टरी और कंपाइलर जाँचें:

xcode-select --print-path
xcrun clang++ --version

Apple का आर्काइव किया हुआ Xcode के साथ कमांड लाइन से बिल्ड करने की FAQ चुनी हुई डेवलपर डायरेक्टरी की जाँच और डेवलपर टूल खोजने या चलाने के लिए xcrun के उपयोग—दोनों का दस्तावेज़ीकरण करता है। इस उदाहरण में शुरू से अंत तक वही चुना हुआ टूलचेन रखें।

shared-global-demo नाम का एक नया खाली फ़ोल्डर बनाएँ, उसे अपने एडिटर में खोलें और नीचे दी गई चारों फ़ाइलें वहाँ सेव करें। यह एक सिंथेटिक, सिंगल-थ्रेडेड C++ प्रोग्राम है: एक फ़ाइल ग्लोबल की मालिक है, दूसरी उसे संशोधित करती है, और एंट्री पॉइंट उसके मान तथा पहचान—दोनों की जाँच करता है।

साझा हेडर में ऑब्जेक्ट घोषित करें

इसे shared_state.hpp नाम से सेव करें:

#ifndef SHARED_STATE_HPP
#define SHARED_STATE_HPP

namespace demo {
extern int itemCount;

void addItem();
int* itemCountAddress();
}

#endif

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

ऑब्जेक्ट की एक परिभाषा दें

इसे shared_state.cpp नाम से सेव करें:

#include "shared_state.hpp"

namespace demo {
int itemCount = 0;
}

यह demo::itemCount की एकमात्र परिभाषा है। परिभाषा वाली सोर्स फ़ाइल में हेडर शामिल करने से कंपाइलर जाँच सकता है कि परिभाषा घोषणा से मेल खाती है। स्पष्ट इनिशियलाइज़र शुरुआती मान को साफ़ तौर पर बताता है।

किसी दूसरी ट्रांसलेशन यूनिट से उसे संशोधित करें

इसे worker.cpp नाम से सेव करें:

#include "shared_state.hpp"

namespace demo {
void addItem() {
    ++itemCount;
}

int* itemCountAddress() {
    return &itemCount;
}
}

इसे main.cpp नाम से सेव करें:

#include "shared_state.hpp"
#include <iostream>

int main() {
    std::cout << "before: " << demo::itemCount << '\n';
    demo::addItem();

    const bool sameObject =
        demo::itemCountAddress() == &demo::itemCount;

    std::cout << "after: " << demo::itemCount << '\n';
    std::cout << std::boolalpha
              << "same object: " << sameObject << '\n';

    return (demo::itemCount == 1 && sameObject) ? 0 : 1;
}

एड्रेस तुलना सफल लिंकिंग से आगे बढ़कर एक उपयोगी जाँच देती है: worker.cpp वह एड्रेस लौटाता है जो उसे दिखता है, और main.cpp उसकी तुलना अपने एड्रेस से करता है। एग्ज़िट स्टेटस संशोधित मान भी जाँचता है। इनमें से कोई भी जाँच इस बात पर निर्भर नहीं करती कि assertions सक्षम हैं या नहीं।

अलग-अलग संकलित करें, फिर तीनों ऑब्जेक्ट फ़ाइलें लिंक करें

Terminal में उस फ़ोल्डर पर जाएँ जिसमें ये चारों फ़ाइलें हैं। बिल्ड का यह पूरा ब्लॉक चलाएँ:

(
    set -e
    xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
        -c shared_state.cpp -o shared_state.o
    xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
        -c worker.cpp -o worker.o
    xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
        -c main.cpp -o main.o
    xcrun clang++ shared_state.o worker.o main.o -o shared-demo
    ./shared-demo
)

Clang के दस्तावेज़ बताते हैं कि -c बिना लिंक किए ऑब्जेक्ट फ़ाइल बनाता है; स्टेज चुनने वाला विकल्प न देने पर ड्राइवर लिंक करता है। स्पष्ट -std=c++17 भाषा मोड चुनता है। Clang कंपाइलर कमांड गाइड देखें। सबशेल कमांड विफल होने पर रुक जाता है, जिससे विफल बिल्ड के बाद पुराना एक्ज़ीक्यूटेबल चलने से बचता है।

सोर्स से निकला अपेक्षित आउटपुट—Mac पर चलाकर रिकॉर्ड किया गया आउटपुट नहीं—यह है:

before: 0
after: 1
same object: true

रिसर्च एनवायरनमेंट में Mac कंपाइलर उपलब्ध नहीं था, इसलिए इस उदाहरण की जाँच की गई, इसे चलाया नहीं गया। ये तीन संकलन कमांड जानबूझकर ऑब्जेक्ट-फ़ाइल की सीमा स्पष्ट करते हैं: shared_state.o परिभाषा देता है, जबकि worker.o और main.o उसका उपयोग करते हैं।

C++17 का inline variable कब इस्तेमाल करना चाहिए?

जब वेरिएबल की परिभाषा साझा हेडर में होनी चाहिए, तब C++17 inline variable चुनें। Clang अपनी C++17 भाषा सुविधाओं में inline variables सूचीबद्ध करता है। भाषा निर्दिष्ट करती है कि बाहरी लिंकेज वाले inline variable को कई ट्रांसलेशन यूनिट में परिभाषित किया जा सकता है, फिर भी वह एक ही एड्रेस वाला एक ही एंटिटी रहता है। Inline स्पेसिफ़ायर देखें।

इसी उदाहरण के लिए, shared_state.hpp की पूरी सामग्री को इस संस्करण से बदलें:

#ifndef SHARED_STATE_HPP
#define SHARED_STATE_HPP

namespace demo {
inline int itemCount = 0;

void addItem();
int* itemCountAddress();
}

#endif

worker.cpp और main.cpp को यथावत रखें। इस संस्करण के बिल्ड से shared_state.cpp हटाएँ: अब उसकी अलग परिभाषा की ज़रूरत नहीं है। दोनों उपयोगकर्ता फ़ाइलों को फिर से संकलित करें और केवल उनकी ऑब्जेक्ट फ़ाइलें लिंक करें:

(
    set -e
    xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
        -c worker.cpp -o worker.o
    xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
        -c main.cpp -o main.o
    xcrun clang++ worker.o main.o -o shared-demo
    ./shared-demo
)

इस संस्करण का अपेक्षित आउटपुट भी वही है। इसकी लिंक कमांड में पुरानी shared_state.o शामिल न करें। पुरानी ऑब्जेक्ट फ़ाइलों को स्पष्ट लिंक सूची से बाहर रखने पर स्वामित्व में हुआ बदलाव साफ़ दिखता है।

Inline परिभाषाओं को one-definition rule की संगतता संबंधी आवश्यकताएँ पूरी करनी चाहिए, जिनमें टोकन क्रम और संबंधित नाम लुकअप का मेल शामिल है। ऐसे मैक्रो से बचें जो अलग-अलग सोर्स फ़ाइलों में अलग इनिशियलाइज़र बनाते हों। केवल सफल लिंकिंग से नियम का पालन साबित नहीं होता। One-definition rule देखें।

आवश्यकता उपयुक्त तरीका मुख्य शर्त
एक इम्प्लीमेंटेशन फ़ाइल म्यूटेबल स्थिति की मालिक है extern के साथ हेडर घोषणा; एक .cpp परिभाषा मालिकाना ऑब्जेक्ट को ठीक एक बार लिंक करें
साझा परिभाषा हेडर में होनी चाहिए C++17 inline variable हर उपयोगकर्ता ट्रांसलेशन यूनिट संगत भाषा मोड और एक जैसी परिभाषा का उपयोग करे
हर सोर्स फ़ाइल जानबूझकर अलग स्थिति की मालिक है नेमस्पेस-स्कोप static या अनाम नेमस्पेस ये आंतरिक लिंकेज बनाते हैं, इसलिए साझा-ऑब्जेक्ट वाला काम नहीं करते

आंतरिक लिंकेज का यह अंतर वर्किंग ड्राफ़्ट के प्रोग्राम और लिंकेज पर आधारित है। इस छोटे मल्टी-फ़ाइल प्रोग्राम में extern तरीका स्वामित्व को जाँचना खास तौर पर आसान बनाता है।

कंपाइलर फ़्लैग बदलने से पहले विफलता का निदान करें

स्वामित्व की समस्या और बिल्ड इनपुट की कमी में अंतर करने के लिए यह क्रम अपनाएँ। ये जाँच ऊपर इस्तेमाल किए गए घोषणा नियमों और संकलन चरणों पर आधारित हैं; निदान का शब्दांकन टूलचेन के अनुसार अलग हो सकता है।

  1. अगर संकलन विफल हो, तो पहले घोषणाएँ जाँचें। पुष्टि करें कि हर सोर्स shared_state.hpp शामिल करती है और एक ही नेमस्पेस, टाइप और स्पेलिंग का उपयोग करती है। लिंकिंग बिना घोषित नाम या परस्पर विरोधी घोषणाओं को ठीक नहीं कर सकती।
  2. अगर लिंकिंग डुप्लिकेट सिंबल की रिपोर्ट करे, तो परिभाषाएँ और लिंक इनपुट गिनें। हेडर में इनिशियलाइज़र, दूसरी .cpp परिभाषा, या एक ही मालिकाना ऑब्जेक्ट दो बार दिए जाने की जाँच करें। Inline संस्करण में पुष्टि करें कि पुरानी नॉन-इनलाइन परिभाषा मौजूद नहीं है।
  3. अगर लिंकिंग undefined symbol की रिपोर्ट करे, तो गायब मालिक खोजें। extern संस्करण में पुष्टि करें कि shared_state.cpp सफलतापूर्वक संकलित हुआ और shared_state.o अंतिम लिंक कमांड में मौजूद है। केवल घोषणा से परिभाषा नहीं मिलती।
  4. अगर लिंकिंग सफल हो लेकिन पहचान की जाँच विफल हो, तो लिंकेज देखें। नेमस्पेस-स्कोप static वेरिएबल या अनाम नेमस्पेस के भीतर परिभाषाएँ खोजें। उनसे हर ट्रांसलेशन यूनिट को अपना अलग ऑब्जेक्ट मिलता है।
  5. हेडर या स्वामित्व की रणनीति बदलने के बाद, सभी प्रभावित ऑब्जेक्ट फिर से बनाएँ। पूरा लागू बिल्ड ब्लॉक दोहराएँ, ताकि कॉलर और परिभाषा देने वाली फ़ाइल वर्तमान घोषणाओं का उपयोग करें।

दो शॉर्टकट पर खास ध्यान देना चाहिए। पहला, हेडर में ग्लोबल की जगह नेमस्पेस-स्कोप static int itemCount = 0; रखने से हर ट्रांसलेशन यूनिट के लिए अलग ऑब्जेक्ट बनता है। दूसरा, साधारण नेमस्पेस-स्कोप const वेरिएबल में आम तौर पर आंतरिक लिंकेज होता है, जब तक कोई अपवाद लागू न हो, जैसे स्पष्ट extern घोषणा या inline। इच्छित लिंकेज का जानबूझकर उपयोग करें; संबंधित अपवाद प्रोग्राम और लिंकेज में दिए गए हैं।

भाषा लिंकेज भी बनाए रखें। extern "C" C भाषा लिंकेज निर्दिष्ट करता है; यह गायब परिभाषा नहीं देता और न ही डुप्लिकेट परिभाषाओं की अनुमति देता है। इस पूरी तरह C++ वाले उदाहरण के लिए डिफ़ॉल्ट C++ लिंकेज रखें। वर्किंग ड्राफ़्ट लिंकेज स्पेसिफ़िकेशन्स में भाषा लिंकेज और नामकरण तथा कॉलिंग कन्वेंशन से जुड़े उसके इम्प्लीमेंटेशन-विशिष्ट गुणों की व्याख्या करता है।

Mac ऐप में Swift भी होने पर क्या बदलता है?

C++ स्टोरेज व्यवस्था C++ इम्प्लीमेंटेशन में ही रहती है। Swift में हेडर इम्पोर्ट करने से इंटरफ़ेस मिलता है; बिल्ड में वह C++ इम्प्लीमेंटेशन भी शामिल होना चाहिए जो नॉन-इनलाइन ग्लोबल का मालिक है। Swift की मिश्रित-भाषा प्रोजेक्ट सेटअप गाइड समर्थित C++ API को फ़्रेमवर्क के पब्लिक हेडर या ऐप टारगेट के ब्रिजिंग हेडर के ज़रिए उपलब्ध कराने का वर्णन करती है।

ऐसे Xcode टारगेट के लिए जिसे Swift से C++ कॉल करना है, दस्तावेज़ीकृत सेटिंग C++ and Objective-C interoperability है, जिसे C++ / Objective-C++ पर सेट किया जाता है। इसे उपयोगकर्ता टारगेट के लिए सक्षम करें और उपयुक्त हेडर उपलब्ध कराएँ। यह ग्लोबल को एक परिभाषा देने की ज़रूरत से अलग आवश्यकता है। Swift और C++ के मिश्रित-भाषा प्रोजेक्ट सेट करना देखें।

दस्तावेज़ीकृत संगतता सीमाएँ बनाए रखें: Swift C++ इंटरऑपरेबिलिटी के लिए Swift 5.9 या बाद का संस्करण चाहिए, मिश्रित Swift और C++ कोड में एक ही C++ स्टैंडर्ड लाइब्रेरी का उपयोग होना चाहिए, और Apple प्लेटफ़ॉर्म libc++ का उपयोग करते हैं। इस लेख का inline variable वाला हेडर इम्पोर्ट करने के लिए C++ सोर्स के साथ-साथ इम्पोर्टर के लिए भी C++17 या नया संस्करण चुनना होगा। Swift अपने भाषा-मानक नियंत्रणों और कंपाइलर सीमाओं का दस्तावेज़ीकरण C++ इंटरऑपरेबिलिटी की समर्थित सुविधाएँ और सीमाएँ में करता है।

यहाँ के उदाहरण एक नेटिव एक्ज़ीक्यूटेबल में लिंक की गई अलग-अलग संकलित C++ फ़ाइलों को कवर करते हैं। एड्रेस जाँच लागू करते समय इसी दायरे को ध्यान में रखें; अलग से लोड किए गए फ़्रेमवर्क और लाइब्रेरी के लिए अपना एक्सपोर्ट और लिंकिंग डिज़ाइन चाहिए। दिखाए गए काम के लिए मालिकाना परिभाषा पहचानें, उसे लिंक में एक बार दें, और जाँचें कि दोनों कॉलर एक ही एड्रेस और अपडेट किया हुआ मान देखते हैं।

स्रोत