macOS पर C++ टेम्पलेट कार्यान्वयनों को अलग स्रोत फ़ाइल में रखने के लिए, जहाँ उनकी परिभाषाएँ उपलब्ध हों वहाँ समर्थित प्रकारों को स्पष्ट रूप से इंस्टैंशिएट करें, फिर उस ऑब्जेक्ट फ़ाइल को अपने एक्ज़ीक्यूटेबल से लिंक करें। इस चरण-दर-चरण मार्गदर्शिका में Apple के C++ टूल्स से एक छोटा स्थानीय उदाहरण बनाया गया है।
यह कार्य विशिष्ट है: ऐसा C++ प्रोग्राम कंपाइल करना जिसके टेम्पलेट सदस्य फ़ंक्शन के बॉडी .cpp फ़ाइल में हों। यह उदाहरण दो ठोस प्रकारों को समर्थन देता है। बाद के एक अनुभाग में बताया गया है कि जब कोई Swift डेस्कटॉप ऐप इसी C++ इंटरफ़ेस का उपयोग करता है, तो और किन आवश्यकताओं का ध्यान रखना होता है।
टेम्पलेट कोड को स्रोत फ़ाइल में ले जाने से लिंकिंग क्यों टूटती है?
टेम्पलेट प्रकारों या फ़ंक्शनों के एक परिवार का वर्णन करता है। इसकी कार्यान्वयन फ़ाइल को कंपाइल करने से अपने-आप हर संभावित स्पेशलाइज़ेशन नहीं बनता। किसी क्लास को इंस्टैंशिएट करने से भी उसके हर सदस्य फ़ंक्शन का बॉडी अपने-आप इंस्टैंशिएट नहीं होता; आम तौर पर सदस्य तभी इंस्टैंशिएट होते हैं जब उनकी आवश्यकता हो। इन्हीं अंतरों से समझ आता है कि कॉलर सफलतापूर्वक कंपाइल हो सकता है, लेकिन लिंकिंग के समय अनसुलझे संदर्भ छोड़ सकता है। C++ working draft: Implicit instantiation
मान लें कि किसी हेडर में क्लास टेम्पलेट का लेआउट परिभाषित है और उसके कंस्ट्रक्टर तथा मेथड घोषित हैं। कॉलर उन घोषणाओं को समझ सकता है। लेकिन यदि मेथड के बॉडी केवल किसी दूसरी स्रोत फ़ाइल में हैं, तो कॉलर उनका उपयोग करके आवश्यक फ़ंक्शन जनरेट नहीं कर सकता। कार्यान्वयन फ़ाइल को कॉलर के लिए ज़रूरी ठोस इंस्टैंशिएशन देने होंगे।
स्पष्ट इंस्टैंशिएशन परिभाषा उन इंस्टैंशिएशनों का अनुरोध करती है। extern template घोषणा बताती है कि कोई इंस्टैंशिएशन कहीं और दिया गया है। स्पष्ट इंस्टैंशिएशन परिभाषा देने वाली ट्रांसलेशन यूनिट में कार्यान्वयन की परिभाषाएँ उपलब्ध होनी चाहिए। C++ working draft: Explicit instantiation
प्रकार आर्ग्युमेंट पर किसका नियंत्रण है, इसके अनुसार व्यवस्था चुनें:
| पाठक की आवश्यकता | उपयुक्त व्यवस्था | रखरखाव का परिणाम |
|---|---|---|
| कॉलर कोई भी उपयुक्त प्रकार दे सकते हैं | परिभाषाएँ हेडर में या उसमें शामिल कार्यान्वयन हेडर में रखें | कॉलर नए प्रकार इंस्टैंशिएट कर सकते हैं |
| लाइब्रेरी कुछ ज्ञात प्रकारों को समर्थन देती है | बॉडी स्रोत फ़ाइल में रखें और उन प्रकारों को स्पष्ट रूप से इंस्टैंशिएट करें | समर्थित प्रकार जोड़ते समय एक इंस्टैंशिएशन भी जोड़ें |
| डेस्कटॉप ऐप को केवल कोई ठोस ऑपरेशन चाहिए | चुने हुए स्पेशलाइज़ेशन के चारों ओर एक साधारण C++ रैपर उपलब्ध कराएँ | टेम्पलेट के विकल्प C++ कार्यान्वयन के भीतर रहें |
यह निर्णय-सहायक ऊपर दिए गए इंस्टैंशिएशन नियमों पर आधारित है। इस मार्गदर्शिका के लिए समर्थित प्रकार जानबूझकर केवल int और double तक सीमित हैं।
तीन फ़ाइलों का पूरा उदाहरण बनाएँ
एक नया खाली फ़ोल्डर बनाएँ और नीचे दी गई तीनों फ़ाइलें उसमें साथ रखें। इस कृत्रिम प्रोग्राम में किसी मान में ऑफ़सेट जोड़ा जाता है; छोटे इनपुट से अपेक्षित परिणाम आसानी से जाँचे जा सकते हैं।
Apple के कमांड-लाइन टूल्स तैयार करें
Apple, Xcode या अलग Command Line Tools पैकेज के ज़रिए कंपाइलर और macOS SDK उपलब्ध कराता है। यदि इनमें से कोई भी इंस्टॉल नहीं है, तो Terminal खोलें, नीचे दिया गया कमांड चलाएँ और इंस्टॉलर का संवाद पूरा करें। Xcode पहले से इंस्टॉल हो तो अलग से टूल्स इंस्टॉल करने की ज़रूरत नहीं है। Installing the command-line tools
xcode-select --install
सार्वजनिक इंटरफ़ेस परिभाषित करें
इस पूरी हेडर फ़ाइल को Offset.hpp नाम से सेव करें। क्लास की परिभाषा, जिसमें उसका डेटा सदस्य भी शामिल है, कॉलर के लिए उपलब्ध रखें। केवल कंस्ट्रक्टर और मेथड के बॉडी स्रोत फ़ाइल में ले जाएँ।
#ifndef OFFSET_HPP
#define OFFSET_HPP
namespace demo {
template <typename T>
class Offset {
public:
explicit Offset(T amount);
T apply(T value) const;
private:
T amount_;
};
extern template class Offset<int>;
extern template class Offset<double>;
} // namespace demo
#endif
extern template की ये दोनों पंक्तियाँ दिए गए इंस्टैंशिएशन घोषित करती हैं; वे सदस्य बॉडी उपलब्ध नहीं करातीं। इस क्लास-स्तरीय स्पष्ट इंस्टैंशिएशन व्यवस्था के लिए क्लास टेम्पलेट की परिभाषा उपलब्ध रहनी चाहिए। C++ working draft: Explicit instantiation
बॉडी परिभाषित करें और इंस्टैंशिएशन दें
इस पूरी कार्यान्वयन फ़ाइल को Offset.cpp नाम से सेव करें। सदस्य परिभाषाओं के बाद स्पष्ट इंस्टैंशिएशन परिभाषाएँ रखें।
#include "Offset.hpp"
namespace demo {
template <typename T>
Offset<T>::Offset(T amount) : amount_(amount) {}
template <typename T>
T Offset<T>::apply(T value) const {
return value + amount_;
}
template class Offset<int>;
template class Offset<double>;
} // namespace demo
क्रम मायने रखता है: क्लास-स्तरीय स्पष्ट इंस्टैंशिएशन परिभाषा केवल उन सदस्यों पर लागू होती है जो उस बिंदु तक परिभाषित हो चुके हों। इससे सदस्य टेम्पलेट अपने-आप इंस्टैंशिएट नहीं होते। हर स्पष्ट इंस्टैंशिएशन परिभाषा के लिए एक ही स्रोत फ़ाइल को स्वामी बनाएँ; भाषा के अनुसार प्रोग्राम में टेम्पलेट और आर्ग्युमेंट सेट के लिए ऐसी अधिकतम एक परिभाषा हो सकती है। Explicit instantiation, Template instantiation and specialization: General
दोनों समर्थित प्रकारों को कॉल करें
इस पूरे कॉलर को main.cpp नाम से सेव करें।
#include "Offset.hpp"
#include <iostream>
int main() {
const demo::Offset<int> whole(3);
const demo::Offset<double> fractional(0.5);
const int first = whole.apply(4);
const double second = fractional.apply(2.0);
std::cout << first << '\n' << second << '\n';
return (first == 7 && second == 2.5) ? 0 : 1;
}
प्रोग्राम परिणाम प्रिंट करने के साथ-साथ अपने एग्ज़िट स्टेटस से भी उनकी जाँच करता है। इस उदाहरण की संगति की समीक्षा की गई, लेकिन तैयारी के दौरान इसे कंपाइल नहीं किया गया: शोध परिवेश में C++ कंपाइलर या macOS SDK उपलब्ध नहीं था।
दोनों स्रोत फ़ाइलें कंपाइल करें, फिर दोनों ऑब्जेक्ट फ़ाइलें लिंक करें
Terminal में उस फ़ोल्डर पर जाएँ जिसमें तीनों फ़ाइलें हैं। ये कमांड इसी क्रम में चलाएँ और हर कमांड सफल होने पर ही आगे बढ़ें:
xcrun clang++ -std=c++17 -stdlib=libc++ -Wall -Wextra -pedantic -c Offset.cpp -o Offset.o
xcrun clang++ -std=c++17 -stdlib=libc++ -Wall -Wextra -pedantic -c main.cpp -o main.o
xcrun clang++ -std=c++17 -stdlib=libc++ main.o Offset.o -o template-demo
./template-demo
यहाँ -c बिना लिंकिंग के ऑब्जेक्ट फ़ाइल बनाता है। अंतिम कंपाइलर इनवोकेशन में -c नहीं है और वह दोनों ऑब्जेक्ट फ़ाइलों को जोड़कर एक एक्ज़ीक्यूटेबल बनाता है। ये कमांड डिफ़ॉल्ट पर निर्भर रहने के बजाय स्पष्ट रूप से C++17 और libc++ चुनते हैं। Clang compiler command guide
कृत्रिम इनपुट से निकला अपेक्षित आउटपुट यह है:
7
2.5
ज़रूरी निर्भरता आख़िरी बिल्ड कमांड में दिखाई देती है: main.o फ़ंक्शनों का अनुरोध करता है और Offset.o उनके इंस्टैंशिएशन देता है। बड़े प्रोजेक्ट में सुनिश्चित करें कि Offset.cpp वाला टार्गेट बिल्ड हो और उसकी ऑब्जेक्ट फ़ाइल या लाइब्रेरी एक्ज़ीक्यूटेबल के लिंक चरण तक पहुँचे। केवल उस फ़ाइल का सफलतापूर्वक कंपाइल होना लिंकिंग पूरी नहीं करता।
कॉलर में #include "Offset.cpp" न जोड़ें, यदि आप उसे अलग से भी कंपाइल कर रहे हैं। इस व्यवस्था में सार्वजनिक हेडर शामिल करें और कार्यान्वयन को अपनी अलग ट्रांसलेशन यूनिट के रूप में कंपाइल करें। यदि कॉलर को बॉडी चाहिए, तो पहले दी गई तालिका में वर्णित शामिल किए जाने वाले कार्यान्वयन-हेडर की व्यवस्था चुनें।
गुम इंस्टैंशिएशन की उपयोगी क्रम में जाँच करें
पहले उस सटीक अनसुलझे फ़ंक्शन की पहचान करें, जिसमें उसके टेम्पलेट आर्ग्युमेंट और क्वालिफ़ायर भी शामिल हों। फिर यह क्रम अपनाएँ। ये जाँच ऑब्जेक्ट फ़ाइलें जनरेट करने और उन्हें लिंक करने के बीच के प्रलेखित अंतर तथा स्पष्ट इंस्टैंशिएशन की आवश्यकताओं पर आधारित हैं। Clang compilation stages, C++ explicit instantiation rules
| आपको क्या मिलता है | क्या जाँचें या बदलें |
|---|---|
| दोनों समर्थित प्रकारों के सदस्य अनसुलझे हैं | पुष्टि करें कि अंतिम लिंक कमांड में Offset.o शामिल है |
| नया इस्तेमाल किया गया प्रकार विफल होता है | स्वामी कार्यान्वयन फ़ाइल में उसका संगत इंस्टैंशिएशन और हेडर में घोषणा जोड़ें |
| एक मेथड गुम है | पुष्टि करें कि उसका बॉडी क्लास इंस्टैंशिएशन से पहले है और उसकी घोषणा परिभाषा से मेल खाती है |
| सदस्य टेम्पलेट गुम है | उसकी परिभाषा उपलब्ध रखते हुए, आवश्यक सदस्य-टेम्पलेट इंस्टैंशिएशन अलग से दें |
| डुप्लिकेट परिभाषा का डायग्नोस्टिक दिखता है | बार-बार दी गई इंस्टैंशिएशन परिभाषाओं या एक साथ कंपाइल और शामिल की गई कार्यान्वयन फ़ाइल की जाँच करें |
| डायग्नोस्टिक असंगत आर्किटेक्चर का नाम देता है | लिंक की गई हर ऑब्जेक्ट फ़ाइल और लाइब्रेरी का आर्किटेक्चर जाँचें |
उदाहरण के लिए, कॉलर में demo::Offset<float> का उपयोग करने से ऐसा प्रकार जुड़ता है जिसे यह कार्यान्वयन उपलब्ध नहीं कराता। समर्थन देने के लिए नेमस्पेस के भीतर हेडर में extern template class Offset<float>; और Offset.cpp में बॉडी के बाद template class Offset<float>; जोड़ें, फिर दोनों ऑब्जेक्ट फ़ाइलें दोबारा बिल्ड करें। केवल घोषणा जोड़ने से गुम फ़ंक्शन जनरेट नहीं होते।
यदि LLVM का अलग llvm-nm एक्ज़ीक्यूटेबल इंस्टॉल है और आपके कमांड पाथ में उपलब्ध है, तो नीचे दिए गए वैकल्पिक कमांड उदाहरण की ऑब्जेक्ट फ़ाइलों की जाँच करते हैं:
llvm-nm --demangle --undefined-only main.o
llvm-nm --demangle --defined-only Offset.o
कॉलर के अनसुलझे सिंबल और कार्यान्वयन के परिभाषित सिंबल में मेल खाते demo::Offset सदस्य नाम देखें। अन्य प्रविष्टियाँ भी अपेक्षित हैं, जिनमें स्टैंडर्ड लाइब्रेरी के संदर्भ शामिल हैं। LLVM दोनों फ़िल्टर और नाम डिमैंगलिंग का दस्तावेज़ देता है; इन कमांड के लिए खास तौर पर llvm-nm चाहिए, जिसकी उपलब्धता Apple टूल्स इंस्टॉल होने से सुनिश्चित नहीं होती। llvm-nm command guide
macOS आर्किटेक्चर और ABI संगतता बनाए रखें
स्पष्ट इंस्टैंशिएशन ठोस टेम्पलेट कोड देता है; इससे एक ऑब्जेक्ट फ़ाइल हर आर्किटेक्चर पर इस्तेमाल करने योग्य नहीं बन जाती। Apple के यूनिवर्सल बाइनरी वर्कफ़्लो में आर्किटेक्चर-विशिष्ट वर्ज़न कंपाइल और लिंक करने के बाद उन्हें जोड़ा जाता है। यदि ऐप arm64 और x86_64 दोनों को समर्थन देता है, तो दोनों बिल्ड में इंस्टैंशिएशन स्रोत शामिल करें। Building a universal macOS binary
नमूने के यूनिवर्सल बिल्ड के लिए नीचे दिए गए कमांड से तैयार एक्ज़ीक्यूटेबल की जाँच करें। पुष्टि करें कि बताए गए आर्किटेक्चर इच्छित बिल्ड से मेल खाते हैं; पहले दिए गए सामान्य बिल्ड कमांड यूनिवर्सल बाइनरी का अनुरोध नहीं करते।
xcrun lipo -archs ./template-demo
मेथड बॉडी को अलग रखने पर भी क्लास का लेआउट हेडर में दिखाई देता है। लेआउट में बदलाव को इंटरफ़ेस में बदलाव मानें, जिसके लिए निर्भर कोड को दोबारा बिल्ड करना आवश्यक है। स्पष्ट इंस्टैंशिएशन मौजूद होने से स्थिर बाइनरी इंटरफ़ेस की गारंटी न मानें। libc++ विशिष्ट संगतता गारंटियों और कॉन्फ़िगरेशन सीमाओं का दस्तावेज़ देता है, और इन गारंटियों को उपयोगकर्ता-परिभाषित फ़ंक्शनों की गारंटियों से स्पष्ट रूप से अलग बताता है। libc++’s ABI Guarantees
Swift ऐप C++ कोड को कॉल करे तो क्या बदलता है?
दो आवश्यकताओं को अलग रखें: Swift को समर्थित C++ घोषणा इंपोर्ट करनी चाहिए और अंतिम एक्ज़ीक्यूटेबल को उसका कार्यान्वयन लिंक करना चाहिए। Swift के दस्तावेज़ C++ क्लास टेम्पलेट के ठोस स्पेशलाइज़ेशन को C++ टाइप एलियास के ज़रिए उपलब्ध कराते हैं; बिना इंस्टैंशिएट किया गया टेम्पलेट सीधे Swift टाइप के रूप में उपलब्ध नहीं है। Mixing Swift and C++: Using Class Templates
नमूना इंटरफ़ेस के लिए, नीचे दिया गया वैकल्पिक हेडर अंश क्लास परिभाषा के बाद namespace demo के भीतर जोड़ा जा सकता है। यह ठोस प्रकार को इंपोर्ट के लिए एक नाम देता है:
using IntOffset = Offset<int>;
यह एलियास प्रकार का नाम देता है। Offset.cpp में स्पष्ट इंस्टैंशिएशन अब भी आउट-ऑफ़-लाइन कंस्ट्रक्टर और मेथड उपलब्ध कराता है। Swift, Clang मॉड्यूल का उपयोग करके C++ हेडर इंपोर्ट करता है और फ़िलहाल C++20 मॉड्यूल इंपोर्ट नहीं कर सकता। प्रोजेक्ट के लिए हेडर-आधारित इंटरऑपरेबिलिटी सेटअप अपनाएँ। Mixing Swift and C++
Xcode में Swift टार्गेट के लिए C++ and Objective-C interoperability को C++ / Objective-C++ मान पर सक्षम करें। उसी टार्गेट से C++ इस्तेमाल करने वाले ऐप टार्गेट में C++ हेडर को उसके ब्रिजिंग हेडर में शामिल करें। फ़्रेमवर्क टार्गेट समर्थित घोषणाएँ सार्वजनिक हेडर के ज़रिए उपलब्ध कराते हैं। Setting Up Mixed-Language Swift and C++ Projects
Swift और C++ के मिश्रित कोड में एक ही C++ स्टैंडर्ड लाइब्रेरी इस्तेमाल होनी चाहिए; Apple प्लेटफ़ॉर्म पर libc++ इस्तेमाल होती है। इंटरऑपरेबिलिटी के लिए C++14 या नया संस्करण आवश्यक है। इंपोर्ट किए गए हेडर का लैंग्वेज मोड C++ बिल्ड से मेल खाना चाहिए, और अधिक जटिल टेम्पलेट उपलब्ध कराने से पहले समर्थित फ़ीचर की सूची जाँचें। स्पष्ट इंस्टैंशिएशन से Swift की समर्थित भाषा मैपिंग का दायरा नहीं बढ़ता। Supported Features and Constraints of C++ Interoperability
निश्चित प्रकारों के सेट के लिए सार्वजनिक घोषणाओं, इंस्टैंशिएशन सूची और अंतिम लिंक इनपुट को साथ में सँभालें। जब किसी नए कॉलर को दूसरा प्रकार चाहिए, तो या तो वह प्रकार सोच-समझकर जोड़ें या कॉलर की ओर से इंस्टैंशिएशन के लिए कार्यान्वयन उपलब्ध कराएँ। यह चुनाव डेस्कटॉप प्रोजेक्ट बढ़ने पर स्रोत-फ़ाइल की सीमा को समझने योग्य बनाए रखता है।
स्रोत
- Implicit instantiation — C++ working draft
- Explicit instantiation — C++ working draft
- Template instantiation and specialization: General — C++ working draft
- Installing the command-line tools — Apple
- clang — the Clang C, C++, and Objective-C compiler — LLVM Project
- llvm-nm — list LLVM bitcode and object file’s symbol table — LLVM Project
- Building a universal macOS binary — Apple
- libc++’s ABI Guarantees — LLVM Project
- Mixing Swift and C++ — Swift.org
- Setting Up Mixed-Language Swift and C++ Projects — Swift.org
- Supported Features and Constraints of C++ Interoperability — Swift.org