لإبقاء تطبيقات قوالب C++ في ملف مصدر منفصل على macOS، أنشئ نسخًا صريحة للأنواع المدعومة حيث تكون تعريفاتها ظاهرة، ثم اربط ملف الكائن الناتج بملفك التنفيذي. يشرح هذا الدليل إنشاء مثال محلي صغير باستخدام أدوات C++ من Apple.
المطلوب محدد: تجميع برنامج C++ تكون أجسام أعضائه القالبية في ملف .cpp. يدعم المثال نوعين محددين. يشرح قسم لاحق المتطلبات الإضافية عندما يستخدم تطبيق Swift لسطح المكتب واجهة C++ نفسها.
لماذا يؤدي نقل شيفرة القالب إلى ملف مصدر إلى تعذر الربط؟
يصف القالب عائلة من الأنواع أو الدوال. تجميع ملف تنفيذه لا ينتج تلقائيًا كل التخصصات الممكنة. كما أن إنشاء نسخة من صنف لا يؤدي تلقائيًا إلى إنشاء نسخة من جسم كل دالة عضو؛ إذ تُنشأ نسخ الأعضاء عمومًا عند الحاجة إليها. تفسر هذه الفروق كيف يمكن للمتصل أن يُجمَّع بنجاح، مع بقاء مراجع غير محلولة عند الربط. مسودة معيار C++: الإنشاء الضمني للنسخ
تأمل ترويسة تعرّف بنية قالب صنف وتصرّح ببانيه وطرقه. يستطيع المتصل فهم هذه التصريحات. لكن إذا كانت أجسام الطرق موجودة في ملف مصدر آخر فقط، فلن يتمكن المتصل من استخدام تلك الأجسام لإنشاء الدوال المطلوبة. يجب أن يوفر ملف التنفيذ النسخ المحددة التي يحتاج إليها المتصل.
يطلب تعريف الإنشاء الصريح تلك النسخ. ويحدد تصريح extern template أن نسخةً ما يوفرها موضع آخر. يجب أن تكون تعريفات التنفيذ متاحة في وحدة الترجمة التي توفر تعريف الإنشاء الصريح. مسودة معيار C++: الإنشاء الصريح للنسخ
اختر الترتيب بناءً على الجهة التي تتحكم في وسائط الأنواع:
| متطلب المستخدم | الترتيب المناسب | أثره على الصيانة |
|---|---|---|
| يمكن للمتصلين تقديم أنواع مناسبة اعتباطية | ضع التعريفات في الترويسة، أو في ترويسة تنفيذية مضمنة منها | يستطيع المتصلون إنشاء نسخ لأنواع جديدة |
| تدعم المكتبة مجموعة معروفة من الأنواع | أبقِ الأجسام في ملف مصدر وأنشئ نسخًا صريحة لتلك المجموعة | أضف نسخة عند إضافة نوع مدعوم |
| يحتاج تطبيق سطح المكتب إلى عملية محددة فقط | اكشف غلاف C++ عاديًا حول تخصص مختار | أبقِ اختيارات القوالب داخل تنفيذ C++ |
ينبني هذا الدليل لاتخاذ القرار على قواعد إنشاء النسخ المذكورة أعلاه. في هذا الدليل، تقتصر المجموعة المدعومة عمدًا على int وdouble.
أنشئ مثالًا كاملًا من ثلاثة ملفات
استخدم مجلدًا جديدًا فارغًا واحفظ الملفات الثلاثة التالية فيه. يضيف البرنامج التجريبي إزاحةً إلى قيمة؛ وتُبقي المدخلات الصغيرة النتائج المتوقعة سهلة الفحص.
جهّز أدوات سطر الأوامر من Apple
توفر Apple المترجم وحزمة تطوير برمجيات macOS من خلال Xcode أو حزمة أدوات سطر الأوامر المستقلة. إذا لم يكن أي منهما مثبتًا، فافتح Terminal وشغّل الأمر التالي، ثم أكمل خطوات التثبيت في النافذة التي تظهر. لا حاجة إلى تثبيت أدوات منفصلة إذا كان Xcode مثبتًا بالفعل. تثبيت أدوات سطر الأوامر
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++: الإنشاء الصريح للنسخ
عرّف الأجسام ووفّر النسخ
احفظ هذا التنفيذ كاملًا باسم 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
الترتيب مهم: يغطي تعريف الإنشاء الصريح على مستوى الصنف الأعضاء المعرّفة حتى موضعه فقط. كما أنه لا ينشئ تلقائيًا نسخًا من قوالب الأعضاء. خصص ملف مصدر واحدًا يملك كل تعريف إنشاء صريح؛ فاللغة تسمح بتعريف واحد كحد أقصى لكل قالب ومجموعة وسائط في البرنامج. الإنشاء الصريح للنسخ، إنشاء القوالب وتخصصها: عام
استدعِ كلا النوعين المدعومين
احفظ هذا المتصل كاملًا باسم 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.
جمّع ملفي المصدر، ثم اربط ملفي الكائن
في 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
المخرجات المتوقعة، المستنتجة من مدخلات البرنامج التجريبي، هي:
7
2.5
يظهر الاعتماد الأساسي في أمر البناء الأخير: يطلب main.o الدوال، ويوفر Offset.o نسخها. في مشروع أكبر، تأكد من بناء الهدف الذي يحتوي على Offset.cpp ووصول ملف الكائن أو المكتبة منه إلى خطوة ربط الملف التنفيذي. تجميع ذلك الملف وحده لا يُكمل عملية الربط.
لا تضف #include "Offset.cpp" إلى المتصل إذا كنت ستجمعه أيضًا بصورة منفصلة. في هذا الترتيب، أدرج الترويسة العامة واجمع ملف التنفيذ بوصفه وحدة ترجمة مستقلة. إذا احتاج المتصلون إلى الأجسام، فاختر عمدًا ترتيب ترويسة التنفيذ المضمّنة الوارد في الجدول السابق.
شخّص النسخ المفقودة بترتيب مفيد
حدد أولًا الدالة غير المحلولة بدقة، بما في ذلك وسائط قالبها والمحددات. ثم اتبع التسلسل التالي. تطبق هذه الفحوصات الفرق الموثق بين إنشاء ملفات الكائن والربط، إلى جانب متطلبات الإنشاء الصريح للنسخ. مراحل ترجمة Clang، قواعد الإنشاء الصريح للنسخ في C++
| ما تجده | ما ينبغي فحصه أو تغييره |
|---|---|
| أعضاء النوعين المدعومين كلاهما غير محلولين | تأكد من ظهور Offset.o في أمر الربط النهائي |
| يفشل استخدام نوع جديد | أضف النسخة المطابقة إلى ملف التنفيذ المسؤول والتصريح إلى الترويسة |
| إحدى الطرق مفقودة | تأكد من أن جسمها يسبق إنشاء نسخة الصنف وأن تصريحها يطابق تعريفها |
| قالب عضو مفقود | وفّر نسخة قالب العضو المطلوبة على نحو منفصل، مع إتاحة تعريفه |
| يظهر تشخيص بتعريف مكرر | تحقق من تكرار تعريفات الإنشاء أو من تجميع ملف تنفيذ مع تضمينه |
| يذكر التشخيص بنيةً غير متوافقة | تحقق من بنية كل ملف كائن ومكتبة مرتبطة |
على سبيل المثال، يؤدي تغيير المتصل لاستخدام demo::Offset<float> إلى إدخال نوع لا يوفره هذا التنفيذ. لدعمه، أضف extern template class Offset<float>; داخل مساحة الأسماء في الترويسة وtemplate class Offset<float>; بعد الأجسام في Offset.cpp، ثم أعد بناء ملفي الكائن. فإضافة التصريح وحده لا تنشئ الدوال المفقودة.
إذا كان الملف التنفيذي المنفصل llvm-nm من LLVM مثبتًا ومتاحًا عبر مسار الأوامر، فهذه أوامر اختيارية لفحص ملفات الكائن في المثال:
llvm-nm --demangle --undefined-only main.o
llvm-nm --demangle --defined-only Offset.o
ابحث عن أسماء أعضاء demo::Offset المطابقة في الرموز غير المحلولة للمتصل والرموز المعرّفة في التنفيذ. توجد إدخالات أخرى متوقعة، ومنها مراجع المكتبة القياسية. توثق LLVM خيارات التصفية وفك تشفير الأسماء؛ وتتطلب هذه الأوامر تحديدًا llvm-nm، ولا ينبغي افتراض توفره بمجرد تثبيت أدوات Apple. دليل أوامر llvm-nm
حافظ على توافق البنية وواجهة ABI في macOS
يوفر الإنشاء الصريح شيفرة قالب محددة؛ لكنه لا يجعل ملف كائن واحدًا صالحًا لكل البنى. يجمع سير عمل Apple للملف الثنائي الشامل نسخًا خاصة بكل بنية بعد تجميعها وربطها. إذا كان التطبيق يدعم كلًا من arm64 وx86_64، فأدرج مصدر الإنشاء في كِلا البناءين. إنشاء ملف macOS ثنائي شامل
لفحص بناء شامل للعينة، استخدم الأمر التالي على الملف التنفيذي الناتج. تحقق من مطابقة البنى المعروضة للبناء المقصود؛ فأوامر البناء العادية السابقة لا تطلب ملفًا ثنائيًا شاملًا.
xcrun lipo -archs ./template-demo
يُبقي فصل أجسام الطرق بنية الصنف ظاهرةً في الترويسة أيضًا. تعامل مع تغييرات هذه البنية على أنها تغييرات في الواجهة تستلزم إعادة بناء الشيفرة التابعة. لا تستنتج وجود واجهة ثنائية مستقرة من مجرد وجود إنشاءات صريحة للنسخ. توثق libc++ ضمانات توافق محددة وقيود إعداد، وتميز صراحةً بين هذه الضمانات وضمانات الدوال التي يعرّفها المستخدم. ضمانات ABI في libc++
ما الذي يتغير عندما يستدعي تطبيق Swift شيفرة C++؟
أبقِ متطلبين منفصلين: يجب أن يستورد Swift تصريح C++ مدعومًا، ويجب أن يربط الملف التنفيذي النهائي التنفيذ. تعرض وثائق Swift تخصصات قوالب الأصناف المحددة عبر أسماء أنواع بديلة في C++؛ أما القالب غير المتخصص فلا يتوفر مباشرةً بوصفه نوع Swift. مزج Swift وC++: استخدام قوالب الأصناف
في واجهة المثال، المقتطف التالي اختياري ويمكن إدراجه داخل namespace demo، بعد تعريف الصنف. وهو يمنح النوع المحدد اسمًا لاستيراده:
using IntOffset = Offset<int>;
يسمي هذا الاسم البديل النوع. ولا يزال الإنشاء الصريح في Offset.cpp هو ما يوفر الباني والطريقة المنفذين خارج الترويسة. يستورد Swift ترويسات C++ باستخدام وحدات Clang، ولا يمكنه حاليًا استيراد وحدات C++20. اتبع إعداد التوافق المعتمد على الترويسات في المشروع. مزج Swift وC++
في Xcode، فعّل C++ and Objective-C interoperability بالقيمة C++ / Objective-C++ لهدف Swift. وللهدف الذي يستخدم C++ من الهدف نفسه، أدرج ترويسة C++ في ترويسة الربط. أما أهداف الأطر فتُظهر التصريحات المدعومة عبر الترويسات العامة. إعداد مشاريع Swift وC++ متعددة اللغات
يجب أن تستخدم شيفرة Swift وC++ المختلطة مكتبة C++ القياسية نفسها؛ وتستخدم منصات Apple libc++. يتطلب التوافق الإصدار C++14 أو أحدث. طابق نمط اللغة للترويسات المستوردة مع بناء C++، وتحقق من قائمة الميزات المدعومة قبل إظهار قوالب أكثر تعقيدًا. لا يوسّع الإنشاء الصريح للنسخ تعيينات اللغات التي يدعمها Swift. الميزات المدعومة والقيود المفروضة على توافق C++
لمجموعة ثابتة من الأنواع، حافظ على توافق التصريحات العامة وقائمة الإنشاء ومدخلات الربط النهائية. عندما يحتاج متصل جديد إلى نوع آخر، أضف ذلك النوع عمدًا أو اجعل التنفيذ متاحًا كي ينشئ المتصل نسخته. يحافظ هذا الاختيار على وضوح حدود ملفات المصدر مع نمو مشروع سطح المكتب.
المصادر
- الإنشاء الضمني للنسخ — مسودة معيار C++
- الإنشاء الصريح للنسخ — مسودة معيار C++
- إنشاء القوالب وتخصصها: عام — مسودة معيار C++
- تثبيت أدوات سطر الأوامر — Apple
- clang — مترجم Clang للغات C وC++ وObjective-C — مشروع LLVM
- llvm-nm — سرد جدول رموز ملفات LLVM bitcode والكائن — مشروع LLVM
- إنشاء ملف macOS ثنائي شامل — Apple
- ضمانات ABI في libc++ — مشروع LLVM
- مزج Swift وC++ — Swift.org
- إعداد مشاريع Swift وC++ متعددة اللغات — Swift.org
- الميزات المدعومة والقيود المفروضة على توافق C++ — Swift.org