Agar implementasi template C++ tetap berada dalam file sumber terpisah di macOS, lakukan instansiasi eksplisit untuk tipe yang didukung di tempat definisinya terlihat, lalu tautkan file objek tersebut ke dalam executable Anda. Panduan ini membuat contoh lokal sederhana dengan alat C++ Apple.

Tugasnya spesifik: kompilasi program C++ yang isi fungsi anggota templatenya berada di file .cpp. Contoh ini mendukung dua tipe konkret. Bagian selanjutnya menjelaskan persyaratan tambahan saat aplikasi desktop Swift menggunakan antarmuka C++ yang sama.

Mengapa memindahkan kode template ke file sumber menyebabkan masalah penautan?

Template menjelaskan keluarga tipe atau fungsi. Mengompilasi file implementasinya tidak otomatis menghasilkan setiap spesialisasi yang mungkin. Instansiasi kelas juga tidak otomatis menginstansiasi isi setiap fungsi anggotanya; anggota umumnya diinstansiasi saat diperlukan. Perbedaan ini menjelaskan mengapa pemanggil dapat berhasil dikompilasi, tetapi tetap meninggalkan referensi yang belum terselesaikan saat penautan. Draf kerja C++: Instansiasi implisit

Bayangkan sebuah header yang mendefinisikan tata letak template kelas serta mendeklarasikan konstruktor dan metodenya. Pemanggil dapat memahami deklarasi tersebut. Namun, jika isi metode hanya ada di file sumber lain, pemanggil tidak dapat menggunakan isi tersebut untuk menghasilkan fungsi yang diperlukan. File implementasi harus menyediakan instansiasi konkret yang dibutuhkan pemanggil.

Definisi instansiasi eksplisit meminta instansiasi tersebut. Deklarasi extern template menunjukkan bahwa instansiasi disediakan di tempat lain. Definisi implementasi harus tersedia di unit translasi yang menyediakan definisi instansiasi eksplisit. Draf kerja C++: Instansiasi eksplisit

Pilih susunannya berdasarkan siapa yang mengendalikan argumen tipe:

Kebutuhan pembaca Susunan yang sesuai Dampak pemeliharaan
Pemanggil boleh memberikan tipe sesuai syarat apa pun Letakkan definisi di header, atau di header implementasi yang disertakan olehnya Pemanggil dapat menginstansiasi tipe baru
Pustaka mendukung sekumpulan tipe yang sudah diketahui Simpan isi fungsi di file sumber dan lakukan instansiasi eksplisit untuk tipe-tipe tersebut Tambahkan instansiasi saat menambahkan tipe yang didukung
Aplikasi desktop hanya memerlukan operasi konkret Sediakan wrapper C++ biasa di sekitar spesialisasi yang dipilih Simpan pilihan template di dalam implementasi C++

Panduan keputusan ini mengikuti aturan instansiasi di atas. Untuk panduan ini, kumpulan tipe yang didukung sengaja dibatasi pada int dan double.

Buat contoh lengkap dengan tiga file

Gunakan folder baru yang kosong, lalu simpan tiga file berikut di dalamnya. Program contoh ini menambahkan offset ke sebuah nilai; input kecil memudahkan pemeriksaan hasil yang diharapkan.

Siapkan alat baris perintah Apple

Apple menyediakan compiler dan SDK macOS melalui Xcode atau paket Command Line Tools mandiri. Jika keduanya belum terpasang, buka Terminal dan jalankan perintah berikut, lalu selesaikan dialog pemasang. Anda tidak perlu memasang alat terpisah jika Xcode sudah terpasang. Menginstal alat baris perintah

xcode-select --install

Definisikan antarmuka publik

Simpan header lengkap ini sebagai Offset.hpp. Pertahankan definisi kelas, termasuk anggota datanya, agar tetap terlihat oleh pemanggil. Hanya isi konstruktor dan metode yang dipindahkan ke file sumber.

#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

Dua baris extern template tersebut mendeklarasikan instansiasi yang disediakan; keduanya tidak menyediakan isi fungsi anggota. Definisi template kelas harus tetap dapat dijangkau agar susunan instansiasi eksplisit tingkat kelas ini berfungsi. Draf kerja C++: Instansiasi eksplisit

Definisikan isi fungsi dan sediakan instansiasinya

Simpan implementasi lengkap ini sebagai Offset.cpp. Letakkan definisi instansiasi eksplisit setelah definisi anggota.

#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

Urutannya penting: definisi instansiasi eksplisit tingkat kelas hanya mencakup anggota yang sudah didefinisikan pada titik tersebut. Definisi itu juga tidak otomatis menginstansiasi template anggota. Gunakan satu file sumber sebagai pemilik untuk setiap definisi instansiasi eksplisit; bahasa ini mengizinkan paling banyak satu definisi untuk setiap pasangan template dan argumen dalam program. Instansiasi eksplisit, Instansiasi dan spesialisasi template: Umum

Panggil kedua tipe yang didukung

Simpan pemanggil lengkap ini sebagai 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;
}

Program ini memeriksa hasil melalui status keluarnya sekaligus mencetaknya. Konsistensi contoh ini telah diperiksa, tetapi contoh ini tidak dikompilasi saat persiapan: lingkungan riset tidak memiliki compiler C++ atau SDK macOS.

Kompilasi kedua file sumber, lalu tautkan kedua file objek

Di Terminal, pindah ke folder yang berisi ketiga file tersebut. Jalankan perintah ini secara berurutan dan lanjutkan hanya jika setiap perintah sebelumnya berhasil:

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

Di sini, -c menghasilkan file objek tanpa melakukan penautan. Pemanggilan compiler terakhir tidak menyertakan -c dan menggabungkan kedua objek menjadi sebuah executable. Perintah-perintah tersebut secara eksplisit memilih C++17 dan libc++ alih-alih mengandalkan nilai bawaan. Panduan perintah compiler Clang

Output yang diharapkan, berdasarkan input contoh buatan, adalah:

7
2.5

Dependensi penting terlihat pada perintah build terakhir: main.o meminta fungsi-fungsi tersebut, dan Offset.o menyediakan instansiasinya. Dalam proyek yang lebih besar, pastikan target yang memuat Offset.cpp dibangun dan objek atau pustakanya disertakan dalam langkah penautan executable. Keberhasilan kompilasi file itu saja belum menyelesaikan proses penautan.

Jangan tambahkan #include "Offset.cpp" ke pemanggil jika file tersebut juga dikompilasi secara terpisah. Untuk susunan ini, sertakan header publik dan kompilasi implementasi sebagai unit translasi tersendiri. Jika pemanggil memerlukan isi fungsi, pilih susunan header implementasi yang disertakan dari tabel sebelumnya.

Diagnosis instansiasi yang hilang dengan urutan yang berguna

Pertama, identifikasi fungsi yang persis belum terselesaikan, termasuk argumen template dan kualifikatornya. Lalu ikuti urutan ini. Pemeriksaan ini menerapkan perbedaan yang terdokumentasi antara menghasilkan file objek dan menautkannya, beserta persyaratan instansiasi eksplisit. Tahapan kompilasi Clang, Aturan instansiasi eksplisit C++

Temuan Hal yang perlu diperiksa atau diubah
Anggota kedua tipe yang didukung belum terselesaikan Pastikan Offset.o tercantum dalam perintah penautan terakhir
Tipe yang baru digunakan gagal Tambahkan instansiasi yang sesuai ke file implementasi pemilik dan deklarasinya ke header
Satu metode tidak ditemukan Pastikan isinya mendahului instansiasi kelas dan deklarasinya cocok dengan definisinya
Template anggota tidak ditemukan Sediakan instansiasi template anggota yang diperlukan secara terpisah, dengan definisinya terlihat
Muncul diagnostik definisi duplikat Periksa apakah definisi instansiasi diulang atau file implementasi dikompilasi sekaligus disertakan
Diagnostik menyebut arsitektur yang tidak kompatibel Periksa arsitektur setiap objek dan pustaka yang ditautkan

Misalnya, mengubah pemanggil agar menggunakan demo::Offset<float> menambahkan tipe yang tidak disediakan implementasi ini. Untuk mendukungnya, tambahkan extern template class Offset<float>; di dalam namespace header dan template class Offset<float>; setelah isi fungsi di Offset.cpp, lalu bangun ulang kedua objek. Menambahkan deklarasi saja tidak menghasilkan fungsi yang hilang.

Jika executable LLVM terpisah, llvm-nm, telah terpasang dan tersedia di jalur perintah Anda, perintah opsional berikut memeriksa objek dari contoh ini:

llvm-nm --demangle --undefined-only main.o
llvm-nm --demangle --defined-only Offset.o

Cari nama anggota demo::Offset yang cocok di simbol belum terselesaikan pada pemanggil dan simbol terdefinisi pada implementasi. Entri lainnya memang diharapkan, termasuk referensi pustaka standar. LLVM mendokumentasikan kedua filter dan penguraian nama; perintah ini secara khusus memerlukan llvm-nm, yang ketersediaannya tidak boleh diasumsikan hanya karena alat Apple sudah terpasang. Panduan perintah llvm-nm

Jaga kompatibilitas arsitektur dan ABI macOS

Instansiasi eksplisit menyediakan kode template konkret; ini tidak membuat satu file objek dapat digunakan untuk semua arsitektur. Alur kerja universal binary Apple mengompilasi dan menautkan versi khusus untuk tiap arsitektur sebelum menggabungkannya. Jika aplikasi mendukung arm64 dan x86_64, sertakan sumber instansiasi dalam kedua build. Membangun binary macOS universal

Untuk build universal contoh ini, periksa executable yang dihasilkan dengan perintah berikut. Pastikan arsitektur yang dilaporkan sesuai dengan build yang dimaksud; perintah build biasa sebelumnya tidak meminta binary universal.

xcrun lipo -archs ./template-demo

Memisahkan isi metode juga membuat tata letak kelas tetap terlihat di header. Perlakukan perubahan pada tata letak tersebut sebagai perubahan antarmuka yang mengharuskan kode yang bergantung padanya dibangun ulang. Jangan menyimpulkan adanya antarmuka biner yang stabil hanya karena instansiasi eksplisit tersedia. libc++ mendokumentasikan jaminan kompatibilitas dan batas konfigurasi tertentu, serta secara tegas membedakan jaminan tersebut dari jaminan atas fungsi yang ditentukan pengguna. Jaminan ABI libc++

Apa yang berubah saat aplikasi Swift memanggil kode C++?

Pisahkan dua persyaratan: Swift harus dapat mengimpor deklarasi C++ yang didukung, dan executable akhir harus menautkan implementasinya. Dokumentasi Swift menunjukkan spesialisasi template kelas konkret melalui alias tipe C++; template yang belum diinstansiasi tidak tersedia secara langsung sebagai tipe Swift. Memadukan Swift dan C++: Menggunakan Template Kelas

Untuk antarmuka contoh ini, fragmen header berikut bersifat opsional dan dapat disisipkan di dalam namespace demo, setelah definisi kelas. Fragmen ini memberi nama pada tipe konkret agar dapat diimpor:

using IntOffset = Offset<int>;

Alias itu memberi nama pada tipe. Instansiasi eksplisit dalam Offset.cpp tetap menyediakan konstruktor dan metode yang didefinisikan di luar kelas. Swift mengimpor header C++ menggunakan modul Clang dan saat ini tidak dapat mengimpor modul C++20. Ikuti penyiapan interoperabilitas berbasis header untuk proyek tersebut. Memadukan Swift dan C++

Di Xcode, aktifkan C++ and Objective-C interoperability dengan nilai C++ / Objective-C++ untuk target Swift. Untuk target aplikasi yang menggunakan C++ dari target yang sama, sertakan header C++ di header penghubungnya. Target framework mengekspos deklarasi yang didukung melalui header publik. Menyiapkan Proyek Swift dan C++ dengan Bahasa Campuran

Kode campuran Swift dan C++ harus menggunakan pustaka standar C++ yang sama; platform Apple menggunakan libc++. Interoperabilitas memerlukan C++14 atau yang lebih baru. Sesuaikan mode bahasa header yang diimpor dengan build C++, dan periksa daftar fitur yang didukung sebelum mengekspos template yang lebih rumit. Instansiasi eksplisit tidak memperluas pemetaan bahasa yang didukung Swift. Fitur yang Didukung dan Batasan Interoperabilitas C++

Untuk sekumpulan tipe tetap, kelola deklarasi publik, daftar instansiasi, dan input penautan akhir secara bersamaan. Saat pemanggil baru membutuhkan tipe lain, tambahkan tipe itu secara sengaja atau sediakan implementasinya agar dapat diinstansiasi oleh pemanggil. Pilihan tersebut menjaga agar batas antar file sumber tetap mudah dipahami saat proyek desktop berkembang.

Sumber