Isytiharkan global dengan extern dalam pengepala, takrifkannya dalam tepat satu fail .cpp, dan pautkan objek fail itu dengan setiap pemanggil. Panduan ini menunjukkan binaan macOS yang lengkap, pemeriksaan alamat dan masa yang sesuai untuk menggunakan pemboleh ubah inline C++17.

Mengapakah global dalam pengepala menyebabkan takrifan pendua?

Bagi program Mac natif yang kecil, tugasnya adalah membolehkan beberapa fail sumber yang dikompil secara berasingan membaca dan mengubah suai objek yang sama. Mulakan dengan membezakan pengisytiharan daripada takrifan: extern int itemCount; mengisytiharkan objek, manakala int itemCount = 0; mentakrifkannya. Menambah pemula pada extern, seperti dalam extern int itemCount = 0;, juga menjadikannya satu takrifan. Draf kerja C++ mendokumentasikan perbezaan ini dalam Pengisytiharan dan takrifan.

Pengepala yang mengandungi takrifan biasa pada skop ruang nama boleh memperkenalkan takrifan itu ke dalam setiap fail sumber yang menyertakannya. Bagi pemboleh ubah bukan inline dan bukan templat dengan pautan luaran, berbilang takrifan merentas unit terjemahan melanggar peraturan satu takrifan. Pemaut mungkin melaporkan simbol pendua, tetapi bahasa ini tidak mewajibkan setiap pelanggaran sedemikian didiagnosis. Lihat Peraturan satu takrifan dalam draf kerja.

Pengawal sertaan menghalang penyertaan berulang dalam satu unit terjemahan. Ia tidak menyelaraskan fail sumber yang dikompil secara berasingan. Oleh itu, pengepala yang dilindungi masih boleh menyebabkan berbilang takrifan merentas fail objek. Panduan arahan pengkompil Clang menerangkan pengembangan pengepala semasa prapemprosesan dan pemautan fail objek selepas itu.

Bina contoh global kongsi yang lengkap pada Mac anda

Gunakan sama ada Xcode atau pakej Command Line Tools for Xcode daripada Apple. Apple mendokumentasikan pakej kendiri itu sebagai alternatif untuk pembangunan di luar Xcode; pemasangan penuh Xcode sudah menyertakan alatan tersebut. Jika kedua-duanya belum dipasang, jalankan arahan berikut dan selesaikan dialog pemasangan. Lihat Memasang alatan baris perintah.

xcode-select --install

Semak direktori pembangun dan pengkompil yang dipilih sebelum membina:

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

Soalan Lazim Membina dari Baris Perintah dengan Xcode yang diarkibkan oleh Apple mendokumentasikan cara memeriksa direktori pembangun yang dipilih dan menggunakan xcrun untuk mencari atau menjalankan alatan pembangun. Kekalkan rantai alat yang sama sepanjang contoh ini.

Cipta folder kosong baharu bernama shared-global-demo, buka folder itu dalam editor anda dan simpan empat fail berikut di situ. Ini ialah program C++ sintetik berutas tunggal: satu fail memiliki global, fail lain mengubah suainya, dan titik masuk menyemak nilai serta identitinya.

Isytiharkan objek dalam pengepala kongsi

Simpan sebagai shared_state.hpp:

#ifndef SHARED_STATE_HPP
#define SHARED_STATE_HPP

namespace demo {
extern int itemCount;

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

#endif

Pengepala ini menyediakan pengisytiharan yang digunakan oleh setiap fail sumber. Mengekalkan ruang nama dan jenis dalam satu pengepala kongsi juga mengelakkan keperluan menyelenggara pengisytiharan berasingan yang ditulis secara manual dan mungkin bercanggah.

Berikan objek itu satu takrifan

Simpan sebagai shared_state.cpp:

#include "shared_state.hpp"

namespace demo {
int itemCount = 0;
}

Inilah satu-satunya takrifan demo::itemCount. Menyertakan pengepala dalam fail sumber yang mentakrifkannya membolehkan pengkompil menyemak bahawa takrifan itu sepadan dengan pengisytiharan. Pemula yang dinyatakan secara jelas menjadikan nilai awalnya mudah difahami.

Ubah suainya daripada unit terjemahan lain

Simpan sebagai worker.cpp:

#include "shared_state.hpp"

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

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

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

Perbandingan alamat memberikan pemeriksaan berguna selain kejayaan pemautan: worker.cpp memulangkan alamat yang dilihatnya, dan main.cpp membandingkannya dengan alamatnya sendiri. Status keluar juga menyemak nilai yang telah diubah suai. Kedua-dua pemeriksaan ini tidak bergantung pada pengaktifan penegasan.

Kompil secara berasingan, kemudian pautkan ketiga-tiga fail objek

Dalam Terminal, tukar ke folder yang mengandungi empat fail tersebut. Jalankan blok binaan lengkap ini:

(
    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 mendokumentasikan bahawa -c menghasilkan fail objek tanpa pemautan; jika pilihan pemilihan peringkat ditinggalkan, pemacu akan membuat pemautan. -std=c++17 yang dinyatakan secara jelas memilih mod bahasa. Lihat Panduan arahan pengkompil Clang. Subshell berhenti jika sesuatu arahan gagal, lalu menghalang binaan yang gagal daripada menjalankan boleh laksana lama.

Output yang dijangka, berdasarkan kod sumber dan bukannya dirakam daripada pelaksanaan pada Mac, ialah:

before: 0
after: 1
same object: true

Persekitaran penyelidikan tidak menyediakan pengkompil Mac, jadi contoh ini diperiksa dan bukannya dilaksanakan. Tiga arahan kompilasi itu sengaja menunjukkan sempadan fail objek: shared_state.o membekalkan takrifan, manakala worker.o dan main.o menggunakannya.

Bilakah anda patut menggunakan pemboleh ubah inline C++17?

Pilih pemboleh ubah inline C++17 apabila takrifan pemboleh ubah itu sepatutnya berada dalam pengepala kongsi. Clang menyenaraikan pemboleh ubah inline antara ciri bahasa C++17. Bahasa ini menetapkan bahawa pemboleh ubah inline dengan pautan luaran boleh ditakrifkan dalam berbilang unit terjemahan sambil kekal sebagai satu entiti dengan satu alamat. Lihat Penentu inline.

Bagi contoh yang sama, gantikan keseluruhan kandungan shared_state.hpp dengan versi ini:

#ifndef SHARED_STATE_HPP
#define SHARED_STATE_HPP

namespace demo {
inline int itemCount = 0;

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

#endif

Kekalkan worker.cpp dan main.cpp tanpa perubahan. Buang shared_state.cpp daripada binaan varian ini: takrifan berasingannya tidak lagi diperlukan. Kompil semula kedua-dua pengguna dan pautkan fail objek mereka sahaja:

(
    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
)

Varian ini mempunyai output jangkaan yang sama. Jangan sertakan shared_state.o lama dalam arahan pemautannya. Dengan mengeluarkan fail objek yang sudah tidak digunakan daripada senarai pautan yang dinyatakan secara jelas, perubahan pemilikan menjadi jelas.

Takrifan inline mesti memenuhi keperluan konsistensi peraturan satu takrifan, termasuk jujukan token yang sepadan dan pencarian nama yang sepadan. Elakkan makro yang menghasilkan pemula berbeza dalam fail sumber yang berlainan. Pemautan yang berjaya sahaja tidak membuktikan pematuhan. Lihat Peraturan satu takrifan.

Keperluan Pendekatan yang sesuai Syarat utama
Satu fail pelaksanaan memiliki keadaan boleh ubah Pengisytiharan pengepala dengan extern; satu takrifan .cpp Pautkan objek pemilik tepat sekali
Takrifan kongsi sepatutnya berada dalam pengepala Pemboleh ubah inline C++17 Setiap unit terjemahan yang menggunakannya menggunakan mod bahasa yang serasi dan takrifan yang konsisten
Setiap fail sumber sengaja memiliki keadaan berasingan static pada skop ruang nama atau ruang nama tanpa nama Ini mewujudkan pautan dalaman, maka ia tidak melaksanakan tugas berkongsi objek

Perbezaan pautan dalaman mengikut Program dan pautan dalam draf kerja. Bagi program berbilang fail yang kecil ini, pendekatan extern menjadikan pemilikan sangat mudah diperiksa.

Diagnosis kegagalan sebelum mengubah bendera pengkompil

Gunakan urutan ini untuk membezakan masalah pemilikan daripada input binaan yang tiada. Pemeriksaan ini mengikut peraturan pengisytiharan dan peringkat kompilasi yang digunakan di atas; perkataan dalam diagnostik mungkin berbeza mengikut rantai alat.

  1. Jika kompilasi gagal, semak pengisytiharan dahulu. Pastikan setiap sumber menyertakan shared_state.hpp dan menggunakan ruang nama, jenis serta ejaan yang sama. Pemautan tidak dapat membaiki nama yang belum diisytiharkan atau pengisytiharan yang bercanggah.
  2. Jika pemautan melaporkan simbol pendua, kira takrifan dan input pautan. Cari pemula dalam pengepala, takrifan .cpp kedua, atau objek pemilik yang dibekalkan dua kali. Bagi varian inline, pastikan takrifan bukan inline yang lama sudah tiada.
  3. Jika pemautan melaporkan simbol yang tidak ditakrifkan, cari pemilik yang tiada. Dalam versi extern, pastikan shared_state.cpp berjaya dikompil dan shared_state.o muncul dalam arahan pemautan akhir. Pengisytiharan sahaja tidak membekalkan takrifan.
  4. Jika pemautan berjaya tetapi pemeriksaan identiti gagal, periksa pautan. Cari pemboleh ubah static pada skop ruang nama atau takrifan dalam ruang nama tanpa nama. Ini memberikan setiap unit terjemahan objeknya sendiri.
  5. Selepas mengubah pengepala atau strategi pemilikan, bina semula semua objek yang terjejas. Ulang blok binaan lengkap yang berkenaan supaya pemanggil dan fail yang mentakrifkan objek menggunakan pengisytiharan terkini.

Dua jalan pintas memerlukan perhatian khusus. Pertama, menggantikan global dengan static int itemCount = 0; pada skop ruang nama dalam pengepala menghasilkan objek berasingan bagi setiap unit terjemahan. Kedua, pemboleh ubah biasa const pada skop ruang nama lazimnya mempunyai pautan dalaman melainkan terdapat pengecualian, seperti pengisytiharan extern yang jelas atau inline. Gunakan pautan yang dimaksudkan dengan sengaja; pengecualian berkaitan disenaraikan dalam Program dan pautan.

Kekalkan juga pautan bahasa. extern \"C\" menetapkan pautan bahasa C; ia tidak membekalkan takrifan yang tiada atau membenarkan takrifan pendua. Bagi contoh C++ sepenuhnya ini, kekalkan pautan C++ lalai. Draf kerja menerangkan pautan bahasa serta ciri penamaan dan konvensyen panggilan khusus pelaksanaannya dalam Spesifikasi pautan.

Apakah perubahan apabila app Mac turut mengandungi Swift?

Susunan storan C++ masih perlu ditentukan dalam pelaksanaan C++. Mengimport pengepala ke dalam Swift menyediakan antara muka; binaan itu juga mesti menyertakan pelaksanaan C++ yang memiliki global bukan inline. Panduan persediaan projek bahasa campuran Swift menerangkan cara mendedahkan API C++ yang disokong melalui pengepala awam rangka kerja atau pengepala penghubung sasaran app.

Bagi sasaran Xcode yang memerlukan Swift untuk memanggil C++, tetapan yang didokumenkan ialah C++ and Objective-C interoperability, ditetapkan kepada C++ / Objective-C++. Dayakannya untuk sasaran pengguna dan dedahkan pengepala yang sesuai. Ini ialah keperluan berasingan daripada memastikan global mempunyai satu takrifan. Lihat Menyediakan Projek Swift dan C++ Bahasa Campuran.

Kekalkan had keserasian yang didokumenkan: kebolehoperasian Swift C++ memerlukan Swift 5.9 atau lebih baharu, kod Swift dan C++ bercampur mesti menggunakan pustaka standard C++ yang sama, dan platform Apple menggunakan libc++. Mengimport pengepala yang mengandungi pemboleh ubah inline dalam artikel ini memerlukan pemilihan C++17 atau lebih baharu untuk pengimport serta sumber C++. Swift mendokumentasikan kawalan piawai bahasa dan kekangan pengkompilnya dalam Ciri dan Kekangan Kebolehoperasian C++ yang Disokong.

Contoh di sini merangkumi fail C++ yang dikompil secara berasingan dan dipautkan ke dalam satu boleh laksana natif. Kekalkan skop tersebut semasa menggunakan pemeriksaan alamat; rangka kerja dan pustaka yang dimuatkan secara berasingan memerlukan reka bentuk eksport dan pemautan tersendiri. Bagi tugas yang ditunjukkan, kenal pasti takrifan pemilik, bekalkannya sekali kepada pemaut dan sahkan bahawa kedua-dua pemanggil melihat alamat serta nilai terkini yang sama.

Sumber