
Ringkasan bagian sebelumnya
Karena pembatasan pada kemampuan untuk menggunakan kompiler C ++ 11, dan dari kurangnya alternatif, dorongan ingin menulis implementasi sendiri dari pustaka C ++ 11 standar di atas pustaka C ++ 98 / C ++ 03 yang disertakan dengan kompiler.
Selain file header standar
type_traits ,
utas ,
mutex ,
chrono ,
nullptr.h ditambahkan
yang mengimplementasikan
std :: nullptr_t dan
core.h di mana makro yang terkait dengan
fungsionalitas yang bergantung pada kompiler ditambahkan, serta memperluas pustaka standar.
Tautan ke GitHub dengan hasil untuk hari ini untuk yang tidak sabar dan yang bukan pembaca:
Komitmen dan kritik yang membangun dipersilahkan
Daftar isi
PendahuluanBab 1. Viam supervadet vadensBab 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifBab 3. Menemukan implementasi nullptr yang sempurnaBab 4. C ++ Template Magic....
4.1 Kita mulai dari yang kecil....
4.2 Tentang berapa banyak kesalahan ajaib yang dikompilasi oleh log untuk kita....
4.3 Pointer dan semuanya....
4.4 Apa lagi yang diperlukan untuk pustaka templatBab 5
...
Bab 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Setelah semua kode disisir sedikit dan dibagi dengan header "standar" menjadi
stdex namespace yang terpisah
, saya melanjutkan untuk mengisi
type_traits ,
nullptr.h dan sepanjang
core.h yang sama, yang berisi makro untuk menentukan versi standar yang digunakan oleh kompiler dan mendukungnya
Nullptr asli ,
char16_t ,
char32_t dan
static_assert .
Secara teori, semuanya sederhana - menurut standar C ++
(klausa 14.8), makro
__cplusplus harus ditentukan oleh kompiler dan sesuai dengan versi standar yang didukung:
C++ pre-C++98: #define __cplusplus 1 C++98: #define __cplusplus 199711L C++98 + TR1: #define __cplusplus 199711L
oleh karena itu, kode untuk menentukan ketersediaan dukungan adalah sepele:
#if (__cplusplus >= 201103L)

Bahkan, tidak semuanya begitu sederhana dan sekarang kruk yang menarik dengan menyapu dimulai.
Pertama, tidak semua, atau lebih tepatnya tidak ada, dari penyusun tidak menerapkan standar berikutnya sepenuhnya dan segera. Misalnya, dalam Visual Studio 2013
constexpr tidak ada
untuk waktu yang sangat lama, sementara itu diklaim mendukung C ++ 11 - dengan peringatan bahwa implementasinya tidak lengkap. Yaitu,
otomatis - tolong,
static_assert - sama mudahnya (bahkan dari MS VS sebelumnya), tetapi
constexpr tidak . Kedua, tidak semua kompiler (dan ini bahkan lebih mengejutkan) dengan benar mengekspos definisi ini dan memperbaruinya tepat waktu. Tiba-tiba, dalam kompiler yang sama, Visual Studio
tidak mengubah versi definisi __cplusplus dari versi pertama kompiler, meskipun dukungan penuh untuk C ++ 11 telah lama diumumkan (yang juga tidak benar, yang mana ada sinar ketidakpuasan yang terpisah - segera setelah percakapan tiba pada fungsi spesifik dari "baru" "11 pengembang standar segera mengatakan bahwa tidak ada preprosesor C99, tidak ada" fitur "lainnya. Dan situasinya diperburuk oleh fakta bahwa oleh penyusun standar diizinkan untuk menetapkan definisi ini berbeda dari nilai-nilai di atas, jika mereka tidak sepenuhnya memenuhi standar yang dinyatakan. Akan logis untuk mengasumsikan, misalnya, pengembangan definisi untuk makro yang diberikan (dengan pengenalan fungsi baru, menambah angka yang tersembunyi di balik definisi ini):
standart C++98: #define __cplusplus 199711L
Tetapi pada saat yang sama, tidak ada kompiler populer utama yang "usang" dengan fitur ini.
Karena semua ini (saya tidak takut dengan kata ini), sekarang untuk setiap kompiler non-standar Anda harus menulis cek khusus Anda sendiri untuk mengetahui standar C ++ dan sejauh mana mendukungnya. Berita baiknya adalah kita perlu mempelajari beberapa fungsi kompiler agar berfungsi dengan benar. Pertama, kita sekarang menambahkan versi memeriksa Visual Studio melalui makro
_MSC_VER , unik untuk kompiler ini. Karena di gudang kompiler yang didukung saya ada juga C ++ Borland Builder 6.0, yang pengembangnya, pada gilirannya, sangat tertarik untuk menjaga kompatibilitas dengan Visual Studio (termasuk "fitur" dan bug), maka tiba-tiba ada makro ini juga. Untuk kompiler yang kompatibel dengan dentang, ada
__has_feature makro non-standar
( fitur_name
) , yang memungkinkan Anda untuk mengetahui apakah kompiler mendukung fungsi ini atau itu. Akibatnya, kode ini meningkat ke:
#ifndef __has_feature #define __has_feature(x) 0
Ingin menjangkau lebih banyak kompiler? Kami menambahkan cek untuk Codegear C ++ Builder, yang merupakan pewaris Borland (dalam manifestasi terburuknya, tetapi lebih lanjut tentang itu nanti):
#ifndef __has_feature #define __has_feature(x) 0
Perlu juga dicatat bahwa karena Visual Studio telah mengimplementasikan dukungan
nullptr dari versi kompiler
_MSC_VER 1600 , serta tipe
bawaan char16_t dan
char32_t , kita perlu menangani ini dengan benar. Beberapa cek lagi ditambahkan:
#ifndef __has_feature #define __has_feature(x) 0
Pada saat yang sama, kami akan memeriksa dukungan C ++ 98, karena untuk kompiler tanpa itu tidak akan ada beberapa file header dari perpustakaan standar, dan kami tidak dapat memverifikasi tidak adanya mereka menggunakan kompiler.
Opsi lengkap #ifndef __has_feature #define __has_feature(x) 0
Dan sekarang konfigurasi yang beraneka ragam dari boost mulai muncul dalam ingatan saya di mana banyak pengembang pekerja keras menulis semua makro yang bergantung pada kompiler ini dan membuat peta tentang apa yang didukung dan apa yang bukan oleh kompiler spesifik dari versi tertentu, dari mana saya pribadi merasa tidak nyaman, Saya ingin tidak pernah melihatnya atau menyentuhnya lagi. Tetapi kabar baiknya adalah Anda bisa berhenti di situ. Setidaknya ini cukup bagi saya untuk mendukung kompiler paling populer, tetapi jika Anda menemukan ketidaktepatan atau ingin menambahkan kompiler lain, saya akan dengan senang hati menerima permintaan tarik.
Sebuah pencapaian hebat dibandingkan dengan peningkatan, saya percaya bahwa memungkinkan untuk menjaga penyebaran makro yang bergantung pada kompiler dalam kode, yang membuat kode lebih bersih dan lebih mudah untuk dipahami, dan juga tidak menumpuk puluhan file konfigurasi untuk setiap OS dan untuk setiap kompiler. Kami akan berbicara tentang kelemahan dari pendekatan ini sedikit kemudian.
Pada tahap ini, kita sudah dapat mulai menghubungkan fungsionalitas yang hilang dari 11 standar, dan hal pertama yang kami perkenalkan adalah
static_assert .
static_assert
Kami mendefinisikan struktur
StaticAssertion , yang akan mengambil nilai Boolean sebagai parameter templat - akan ada kondisi kami, jika tidak terpenuhi (ekspresi
salah ), kesalahan akan terjadi dalam kompilasi templat yang tidak ditentukan. Dan struktur boneka lain untuk menerima
sizeof ( StaticAssertion ) .
namespace stdex { namespace detail { template <bool> struct StaticAssertion; template <> struct StaticAssertion<true> { };
dan sihir makro lebih lanjut
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message) #else
penggunaan:
STATIC_ASSERT(sizeof(void*) == 4, non_x32_platform_is_unsupported);
Perbedaan penting antara implementasi saya dan standar adalah bahwa tidak ada kata kunci yang berlebihan tanpa memberi tahu pengguna. Hal ini disebabkan oleh fakta bahwa dalam C ++ tidak mungkin untuk mendefinisikan beberapa definisi dengan jumlah argumen yang berbeda tetapi satu nama, dan implementasi tanpa pesan jauh lebih tidak berguna daripada opsi yang dipilih. Fitur ini mengarah pada fakta bahwa pada dasarnya STATIC_ASSERT dalam implementasi saya adalah versi yang sudah ditambahkan di C ++ 11.
Mari kita lihat apa yang terjadi. Sebagai hasil dari memeriksa versi
__cplusplus dan makro kompiler non-standar, kami memiliki informasi yang cukup tentang dukungan C ++ 11 (dan karenanya
static_assert ), diungkapkan oleh
definisi _STDEX_NATIVE_CPP11_SUPPORT. Oleh karena itu, jika makro ini didefinisikan, kita cukup menggunakan
static_assert standar:
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message)
Harap perhatikan bahwa parameter kedua makro STATIC_ASSERT sama sekali bukan string literal, dan karenanya menggunakan operator preprocessor # kami akan mengonversi parameter pesan menjadi string untuk transmisi ke static_assert standar.
Jika kami tidak memiliki dukungan dari kompiler, maka kami melanjutkan ke implementasi kami. Untuk memulainya, kami akan mendeklarasikan macro tambahan untuk string "gluing" (operator preprocessor
## hanya bertanggung jawab untuk ini).
#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2) #define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2) #define CONCATENATE2(arg1, arg2) arg1##arg2
Saya secara khusus tidak menggunakan secara sederhana #define CONCATENATE ( arg1 , arg2 ) arg1 ## arg2 agar dapat meneruskan hasil dari makro CONCATENATE yang sama sebagai argumen ke arg1 dan arg2 .
Selanjutnya, kami mendeklarasikan struktur dengan nama cantik __static_assertion_at_line_ {nomor baris} (makro
__LINE__ juga ditentukan oleh standar dan harus diperluas ke nomor baris yang dipanggil), dan di dalam struktur ini kami menambahkan bidang jenis
StaticAssertion kami dengan nama STATIC_ASSERTION_FAILED_AT_LINE _ {_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ pesan kesalahan dari makro panggilan}.
#define STATIC_ASSERT(expression, message)\ struct CONCATENATE(__static_assertion_at_line_, __LINE__)\ {\ stdex::detail::StaticAssertion<static_cast<bool>((expression))> CONCATENATE(CONCATENATE(CONCATENATE(STATIC_ASSERTION_FAILED_AT_LINE_, __LINE__), _WITH__), message);\ };\ typedef stdex::detail::StaticAssertionTest<sizeof(CONCATENATE(__static_assertion_at_line_, __LINE__))> CONCATENATE(__static_assertion_test_at_line_, __LINE__)
Dengan parameter templat di
StaticAssertion, kami meneruskan ekspresi yang dicentang di
STATIC_ASSERT , yang mengarahkannya ke
bool . Akhirnya, untuk menghindari membuat variabel lokal dan nol-overhead memeriksa kondisi pengguna, alias dideklarasikan untuk tipe
StaticAssertionTest <sizeof ({nama struktur yang dinyatakan di atas}) dengan nama __static_assertion_test_at_line_ {nomor baris}.
Semua keindahan dengan penamaan diperlukan hanya untuk membuatnya jelas dari kesalahan kompilasi bahwa ini adalah hasil yang tegas, dan bukan hanya kesalahan, tetapi juga untuk menampilkan pesan kesalahan yang ditetapkan untuk pernyataan ini. Trik
sizeof diperlukan untuk memaksa kompiler untuk membuat
instance kelas template
StaticAssertion , yang ada di dalam struktur yang baru saja dinyatakan, dan dengan demikian memeriksa kondisi yang dilewati untuk menegaskan.
STATIC_ASSERT hasilGCC:
30: 103: kesalahan: bidang 'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' memiliki jenis 'stdex :: detail :: StaticAssertion <false>' yang tidak lengkap
25:36: note: dalam definisi makro 'CONCATENATE2'
23:36: catatan: dalam ekspansi makro 'CONCATENATE1'
30:67: note: dalam ekspansi makro 'CONCATENATE'
24:36: note: dalam ekspansi makro 'CONCATENATE2'
23:36: catatan: dalam ekspansi makro 'CONCATENATE1'
30:79: note: dalam ekspansi makro 'CONCATENATE'
24:36: note: dalam ekspansi makro 'CONCATENATE2'
23:36: catatan: dalam ekspansi makro 'CONCATENATE1'
30:91: note: dalam ekspansi makro 'CONCATENATE'
36: 3: note: dalam perluasan makro 'STATIC_ASSERT'
Borland C ++ Builder:
[C ++ Error] stdex_test.cpp (36): E2450 Struktur tidak terdefinisi 'stdex :: detail :: StaticAssertion <0>'
[C ++ Kesalahan] stdex_test.cpp (36): E2449 Ukuran 'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' tidak diketahui atau nol
[C ++ Error] stdex_test.cpp (36): E2450 Struktur tidak terdefinisi 'stdex :: detail :: StaticAssertion <0>'
Visual Studio:
Kesalahan c2079
"Trik" kedua yang ingin saya miliki, sementara
hilang dari standar adalah
countof - menghitung jumlah elemen dalam array. Sishers sangat suka mendeklarasikan makro ini melalui sizeof (arr) / sizeof (arr [0]), tetapi kita akan melangkah lebih jauh.
hitungan
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #include <cstddef> namespace stdex { namespace detail { template <class T, std::size_t N> constexpr std::size_t _my_countof(T const (&)[N]) noexcept { return N; } } // namespace detail } #define countof(arr) stdex::detail::_my_countof(arr) #else //no C++11 support #ifdef _STDEX_NATIVE_MICROSOFT_COMPILER_EXTENSIONS_SUPPORT // Visual C++ fallback #include <stdlib.h> #define countof(arr) _countof(arr) #elif defined(_STDEX_NATIVE_CPP_98_SUPPORT)// C++ 98 trick #include <cstddef> template <typename T, std::size_t N> char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N]; #define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x)) #else #define countof(arr) sizeof(arr) / sizeof(arr[0]) #endif
Untuk kompiler dengan dukungan
constexpr ,
kami akan mendeklarasikan versi constexpr dari template ini (yang sama sekali tidak diperlukan, untuk semua standar, implementasi melalui template
COUNTOF_REQUIRES_ARRAY_ARGUMENT sudah
cukup ), untuk sisanya kami memperkenalkan versi melalui fungsi template
COUNTOF_REQUIRES_ARRAY_ARGUMENT . Visual Studio di sini sekali lagi dibedakan dengan adanya implementasi
_countof di file header
stdlib.h .
Fungsi
COUNTOF_REQUIRES_ARRAY_ARGUMENT terlihat mengintimidasi dan
mencari tahu apa yang dilakukannya cukup rumit. Jika Anda melihat lebih dekat, Anda dapat memahami bahwa dibutuhkan array elemen tipe template
T dan ukuran
N sebagai satu-satunya argumen - dengan demikian, dalam hal mentransfer tipe elemen lain (bukan array), kami mendapatkan kesalahan kompilasi, yang pasti menyenangkan. Melihat lebih dekat, Anda dapat mengetahui (dengan susah payah) bahwa ia mengembalikan array elemen
char ukuran
N. Pertanyaannya adalah, mengapa kita membutuhkan semua ini? Di sinilah operator
sizeof ikut
bermain dan kemampuan uniknya untuk bekerja pada waktu kompilasi. Ukuran panggilan
( COUNTOF_REQUIRES_ARRAY_ARGUMENT ) menentukan ukuran array elemen
char yang dikembalikan oleh fungsi, dan karena
sizeof standar
(char) == 1, ini adalah jumlah elemen
N dalam array asli. Elegan, cantik, dan sepenuhnya gratis.
selamanya
Makro pembantu kecil lain yang saya gunakan di mana pun loop tak terbatas diperlukan
selamanya . Ini didefinisikan sebagai berikut:
#if !defined(forever) #define forever for(;;) #else #define STRINGIZE_HELPER(x) #x #define STRINGIZE(x) STRINGIZE_HELPER(x) #define WARNING(desc) message(__FILE__ "(" STRINGIZE(__LINE__) ") : warning: " desc) #pragma WARNING("stdex library - macro 'forever' was previously defined by user; ignoring stdex macro definition") #undef STRINGIZE_HELPER #undef STRINGIZE #undef WARNING #endif
Sintaksis contoh untuk mendefinisikan loop infinite eksplisit:
unsigned int i = 0; forever { ++i; }
Makro ini digunakan hanya untuk secara eksplisit mendefinisikan loop tak terbatas dan dimasukkan dalam perpustakaan hanya untuk alasan "tambahkan gula sintaksis". Di masa depan, saya mengusulkan untuk menggantinya dengan opsional melalui mendefinisikan makro plug-in
SELAMANYA . Apa yang luar biasa dalam cuplikan kode di atas dari pustaka adalah makro
PERINGATAN yang sama yang menghasilkan pesan peringatan di semua kompiler jika makro
selamanya telah ditentukan oleh pengguna. Ini menggunakan makro
__LINE__ standar yang sudah dikenal dan makro
__FILE__ standar, yang dikonversi menjadi string dengan nama file sumber saat ini.
stdex_assert
Untuk menerapkan
asert dalam runtime,
stdex_assert makro diperkenalkan sebagai:
#if defined(assert) #ifndef NDEBUG #include <iostream> #define stdex_assert(condition, message) \ do { \ if (! (condition)) { \ std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \ << " line " << __LINE__ << ": " << message << std::endl; \ std::terminate(); \ } \ } while (false) #else #define stdex_assert(condition, message) ((void)0) #endif #endif
Saya tidak akan mengatakan bahwa saya sangat bangga dengan implementasi ini (itu akan berubah di masa depan), tetapi teknik yang menarik telah digunakan di sini yang ingin saya perhatikan. Untuk menyembunyikan pemeriksaan dari ruang lingkup kode aplikasi, konstruk
do {} while (false) digunakan, yang akan dieksekusi, yang jelas sekali dan pada saat yang sama tidak akan memperkenalkan kode "layanan" dalam kode aplikasi umum. Teknik ini cukup berguna dan digunakan di beberapa tempat lain di perpustakaan.
Jika tidak, implementasinya sangat mirip dengan pernyataan standar - dengan makro
NDEBUG tertentu, yang biasanya dibuat oleh kompiler dalam rilis build, menegaskan tidak melakukan apa-apa, jika tidak maka program akan terganggu dengan output pesan ke aliran kesalahan standar jika kondisi tegas tidak terpenuhi.
kecuali
Untuk fungsi yang tidak membuang pengecualian, kata kunci
noexcept telah diperkenalkan dalam standar baru. Ini juga cukup sederhana dan tanpa rasa sakit untuk diterapkan melalui makro:
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define stdex_noexcept noexcept #else #define stdex_noexcept throw() #endif
Namun, perlu dipahami bahwa dengan standar
noexcept dapat mengambil nilai
bool , dan juga
dapat digunakan untuk menentukan pada waktu kompilasi bahwa ekspresi yang diteruskan tidak membuang pengecualian. Fungsionalitas ini tidak dapat diimplementasikan tanpa dukungan kompiler, dan oleh karena itu hanya ada
stdex_noexcept "stripped down" di perpustakaan.
Akhir bab kedua.
Bab ketiga akan berbicara tentang seluk-beluk implementasi nullptr, mengapa berbeda untuk kompiler yang berbeda, serta pengembangan type_traits, dan apa bug lain dalam kompiler yang saya temui selama pengembangannya.
Terima kasih atas perhatian anda