
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.
Static_assert , 
noexcept , 
countof diimplementasikan, dan juga, setelah mempertimbangkan semua definisi non-standar dan fitur kompiler, informasi muncul tentang fungsi yang didukung oleh kompiler saat ini. Ini melengkapi deskripsi 
core.h , tetapi itu tidak akan lengkap tanpa 
nullptr .
Tautan ke GitHub dengan hasil untuk hari ini untuk yang tidak sabar dan yang bukan pembaca:
Komitmen dan kritik yang membangun dipersilahkan
Jadi, mari kita lanjutkan.
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 3. Menemukan implementasi nullptr yang sempurna
Setelah seluruh epik dengan makro kompiler non-standar dan penemuan "luar biasa" yang mereka sajikan, saya akhirnya bisa menambahkan 
nullptr dan itu menghangatkan jiwa saya. Akhirnya, Anda dapat menyingkirkan semua perbandingan ini dengan 0 atau bahkan 
NULL .

 Kebanyakan programmer mengimplementasikan 
nullptr sebagai
#define nullptr 0 
dan ini bisa mengakhiri bab ini. Jika Anda menginginkan diri Anda 
nullptr , maka ganti saja 0 dengan definisi seperti itu, karena pada dasarnya ini semua yang diperlukan untuk operasi yang benar.
Jangan lupa untuk benar-benar menulis cek, jika tidak, tiba-tiba orang lain akan ditemukan dengan definisi ini:
 #ifndef nullptr #define nullptr 0 #else #error "nullptr defined already" #endif 
Arahan preprocessor 
#error akan 
menghasilkan kesalahan dengan teks yang dapat dibaca manusia saat kompilasi, dan, ya, ini adalah arahan standar, penggunaannya jarang, tetapi dapat ditemukan.
Tetapi dalam implementasi seperti itu, kita kehilangan salah satu poin penting yang dijelaskan dalam standar, yaitu 
std :: nullptr_t - tipe terpisah, contoh konstan di antaranya adalah 
nullptr . Dan pengembang kromium juga pernah 
mencoba untuk memecahkan masalah ini (sekarang ada kompiler baru dan 
nullptr normal) mendefinisikannya sebagai kelas yang dapat dikonversi ke pointer ke jenis apa pun. Karena, secara standar, ukuran 
nullptr harus sama dengan ukuran pointer ke 
void (dan 
void * juga harus mengandung pointer apa pun, kecuali pointer ke anggota kelas), kami "membakukan" implementasi ini dengan menambahkan pointer nol yang tidak digunakan:
 class nullptr_t_as_class_impl { public: nullptr_t_as_class_impl() { } nullptr_t_as_class_impl(int) { }  
Konversi kelas ini ke penunjuk apa pun disebabkan oleh operator templat jenis itu, yang disebut jika ada sesuatu yang dibandingkan dengan 
nullptr . Yaitu, ekspresi 
char * my_pointer; if (my_pointer == nullptr) akan benar-benar dikonversi menjadi 
if (my_pointer == nullptr.operator char * ()) , yang membandingkan pointer ke 0. Operator tipe kedua diperlukan untuk mengonversi 
nullptr ke pointer ke anggota kelas. Dan di sini Borland C ++ Builder 6.0 “membedakan dirinya”, yang secara tak terduga memutuskan bahwa kedua operator ini identik dan dapat dengan mudah membandingkan pointer ke anggota kelas dan pointer biasa satu sama lain, sehingga ada ketidakpastian setiap kali 
nullptr dibandingkan dengan pointer (ini adalah bug, dan mungkin tidak hanya dengan kompiler ini). Kami sedang menulis implementasi terpisah untuk kasus ini:
 class nullptr_t_as_class_impl1 { public: nullptr_t_as_class_impl1() { } nullptr_t_as_class_impl1(int) { }  
Keuntungan dari tampilan 
nullptr ini adalah bahwa sekarang ada tipe terpisah untuk 
std :: nullptr_t . Kerugian? Konstanta 
nullptr hilang selama kompilasi dan perbandingan melalui operator ternary, kompilator tidak dapat menyelesaikannya.
 unsigned* case5 = argc > 2 ? (unsigned*)0 : nullptr;  
Dan saya ingin "dan checker dan pergi." Solusinya terlintas dalam pikiran hanya satu: 
enum . Anggota enumerasi dalam C ++ akan memiliki tipe terpisah mereka sendiri, dan juga akan dikonversi ke 
int tanpa masalah (dan sebenarnya mereka adalah konstanta integer). Properti anggota enumerasi ini akan membantu kami, karena 0 yang sangat “spesial” yang digunakan sebagai pengganti 
nullptr untuk pointer adalah 
int yang paling umum. Saya belum melihat implementasi 
nullptr di Internet, dan mungkin itu juga sesuatu yang buruk, tetapi saya tidak tahu mengapa. Mari kita tulis implementasi:
 #ifdef NULL #define STDEX_NULL NULL #else #define STDEX_NULL 0 #endif namespace ptrdiff_detail { using namespace std; } template<bool> struct nullptr_t_as_ulong_type { typedef unsigned long type; }; template<> struct nullptr_t_as_ulong_type<false> { typedef unsigned long type; }; template<bool> struct nullptr_t_as_ushort_type { typedef unsigned short type; }; template<> struct nullptr_t_as_ushort_type<false> { typedef nullptr_t_as_long_type<sizeof(unsigned long) == sizeof(void*)>::type type; }; template<bool> struct nullptr_t_as_uint_type { typedef unsigned int type; }; template<> struct nullptr_t_as_uint_type<false> { typedef nullptr_t_as_short_type<sizeof(unsigned short) == sizeof(void*)>::type type; }; typedef nullptr_t_as_uint_type<sizeof(unsigned int) == sizeof(void*)>::type nullptr_t_as_uint; enum nullptr_t_as_enum { _nullptr_val = ptrdiff_detail::ptrdiff_t(STDEX_NULL), _max_nullptr = nullptr_t_as_uint(1) << (CHAR_BIT * sizeof(void*) - 1) }; typedef nullptr_t_as_enum nullptr_t; #define nullptr nullptr_t(STDEX_NULL) 
Seperti yang Anda lihat di sini sedikit lebih banyak kode daripada hanya mendeklarasikan 
enum nullptr_t dengan anggota 
nullptr = 0 . Pertama, mungkin tidak ada definisi 
NULL . Ini harus didefinisikan dalam 
daftar header standar yang agak padat , tetapi seperti yang telah ditunjukkan oleh praktik, lebih baik memainkannya dengan aman dan memeriksa makro ini. Kedua, representasi 
enum di C ++ sesuai dengan standar yang ditentukan implementasi, yaitu tipe enumerasi dapat diwakili oleh tipe integer (dengan ketentuan bahwa tipe-tipe ini tidak boleh lebih dari 
int , asalkan nilai 
enum cocok dengan itu). Misalnya, jika Anda mendeklarasikan 
tes enum {_1, _2}, kompiler dapat dengan mudah menyatakannya sebagai 
pendek, dan sangat mungkin bahwa 
sizeof ( test ) ! = Sizeof (void *) . Agar implementasi 
nullptr mematuhi standar, Anda perlu memastikan bahwa ukuran tipe yang dipilih kompiler untuk 
nullptr_t_as_enum cocok dengan ukuran penunjuk, mis. 
sizeof dasarnya sama 
(void *) . Untuk melakukan ini, menggunakan 
templat nullptr_t_as ... , pilih tipe integer yang akan sama dengan ukuran pointer, dan kemudian atur nilai maksimum elemen dalam enumerasi kami ke nilai maksimum dari tipe integer ini.
Saya ingin memperhatikan CHAR_BIT makro yang ditentukan dalam header iklim standar. Makro ini disetel ke jumlah bit dalam satu karakter , yaitu jumlah bit per byte pada platform saat ini. Definisi standar yang berguna yang tidak perlu dilewati oleh pengembang dengan menempel delapan di mana-mana, meskipun di beberapa tempat dalam satu byte tidak ada 8 bit sama sekali .
Dan fitur lainnya adalah penugasan 
NULL sebagai nilai dari elemen 
enum . Beberapa kompiler memberikan peringatan (dan kekhawatiran mereka dapat dipahami) tentang fakta bahwa 
NULL ditugaskan ke "non-pengindeks". Kami mengambil 
namespace standar ke 
ptrdiff_detail lokal kami, agar tidak mengacaukan sisa namespace, dan kemudian, untuk menenangkan kompilator, kami secara eksplisit mengkonversi 
NULL ke 
std :: ptrdiff_t - jenis lain yang agak kurang digunakan dalam C ++, yang berfungsi untuk mewakili hasil operasi aritmatika (pengurangan) dengan pointer dan biasanya merupakan alias dari tipe 
std :: size_t ( 
std :: intptr_t di C ++ 11).
SFINAE
Di sini, untuk pertama kalinya dalam cerita saya, kita dihadapkan dengan fenomena seperti itu di C ++ karena 
kegagalan substitusi bukanlah kesalahan (SFINAE) . Singkatnya, intinya adalah bahwa ketika kompiler "melewati" fungsi yang sesuai kelebihan untuk panggilan tertentu, ia harus memeriksa semuanya, dan tidak berhenti setelah kegagalan pertama atau setelah kelebihan pertama yang cocok. Dari sini muncul pesannya tentang 
ambiguitas , ketika ada dua kelebihan fungsi yang dipanggil yang identik dari sudut pandang kompiler, serta kemampuan kompiler untuk memilih kelebihan fungsi yang paling tepat untuk panggilan tertentu dengan parameter tertentu. Fitur kompiler ini memungkinkan Anda untuk melakukan bagian terbesar dari semua templat "ajaib" (omong-omong hi 
std :: enable_if ), dan itu juga merupakan dasar dari boost dan library saya.
Karena, sebagai hasilnya, kami memiliki beberapa implementasi 
nullptr, kami menggunakan SFINAE "pilih" yang terbaik pada tahap kompilasi. Kami menyatakan tipe "ya" dan "tidak" untuk memeriksa 
ukuran fungsi penyelidikan yang dinyatakan di bawah ini.
 namespace nullptr_detail { typedef char _yes_type; struct _no_type { char padding[8]; }; struct dummy_class {}; _yes_type _is_convertable_to_void_ptr_tester(void*); _no_type _is_convertable_to_void_ptr_tester(...); typedef void(nullptr_detail::dummy_class::*dummy_class_f)(int); typedef int (nullptr_detail::dummy_class::*dummy_class_f_const)(double&) const; _yes_type _is_convertable_to_member_function_ptr_tester(dummy_class_f); _no_type _is_convertable_to_member_function_ptr_tester(...); _yes_type _is_convertable_to_const_member_function_ptr_tester(dummy_class_f_const); _no_type _is_convertable_to_const_member_function_ptr_tester(...); template<class _Tp> _yes_type _is_convertable_to_ptr_tester(_Tp*); template<class> _no_type _is_convertable_to_ptr_tester(...); } 
Di sini kita akan menggunakan prinsip yang sama seperti pada bab kedua dengan hitungan dan definisinya melalui ukuran nilai pengembalian (array elemen) dari fungsi templat COUNTOF_REQUIRES_ARRAY_ARGUMENT .
 template<class T> struct _is_convertable_to_void_ptr_impl { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_void_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); }; 
Apa yang sedang terjadi di sini? Pertama, kompiler “ 
mengulangi ” kelebihan fungsi 
_is_convertable_to_void_ptr_tester dengan argumen tipe 
T dan nilai 
NULL (nilai tidak berperan, hanya 
NULL yang harus mengetikkan- 
T ). Hanya ada dua kelebihan - dengan tipe 
* kosong dan dengan 
daftar argumen variabel (...) . Mengganti argumen ke masing-masing kelebihan ini, kompiler akan memilih yang pertama jika tipe dilemparkan ke pointer untuk 
membatalkan , dan yang kedua jika gips tidak dapat dilakukan. Dengan kelebihan yang dipilih oleh kompiler, kami menggunakan 
sizeof untuk menentukan ukuran nilai yang dikembalikan oleh fungsi, dan karena mereka dijamin berbeda ( 
sizeof ( _no_type ) == 8 , 
sizeof ( _yes_type ) == 1 ), kami dapat menentukan ukuran kelebihan beban yang diambil oleh kompiler dan karenanya dikonversi apakah tipe kita tidak 
valid * atau tidak.
Kami akan menerapkan templat pemrograman yang sama lebih jauh untuk menentukan apakah objek dari jenis pilihan kami untuk mewakili 
nullptr_t dikonversi ke sembarang penunjuk (pada dasarnya 
(T) ( STDEX_NULL ) adalah definisi masa depan untuk 
nullptr ).
 template<class T> struct _is_convertable_to_member_function_ptr_impl { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_member_function_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)) && (sizeof(nullptr_detail::_is_convertable_to_const_member_function_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); }; template<class NullPtrType, class T> struct _is_convertable_to_any_ptr_impl_helper { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_ptr_tester<T>((NullPtrType) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); }; template<class T> struct _is_convertable_to_any_ptr_impl { static const bool value = _is_convertable_to_any_ptr_impl_helper<T, int>::value && _is_convertable_to_any_ptr_impl_helper<T, float>::value && _is_convertable_to_any_ptr_impl_helper<T, bool>::value && _is_convertable_to_any_ptr_impl_helper<T, const bool>::value && _is_convertable_to_any_ptr_impl_helper<T, volatile float>::value && _is_convertable_to_any_ptr_impl_helper<T, volatile const double>::value && _is_convertable_to_any_ptr_impl_helper<T, nullptr_detail::dummy_class>::value; }; template<class T> struct _is_convertable_to_ptr_impl { static const bool value = ( _is_convertable_to_void_ptr_impl<T>::value == bool(true) && _is_convertable_to_any_ptr_impl<T>::value == bool(true) && _is_convertable_to_member_function_ptr_impl<T>::value == bool(true) ); }; 
Tentu saja, tidak mungkin untuk mengulangi semua petunjuk yang mungkin dan tidak dapat dipahami serta kombinasinya dengan 
pengubah volatile dan 
const , oleh karena itu saya membatasi diri hanya pada 9 pemeriksaan ini (dua pada pointer ke fungsi kelas, satu pada pointer ke 
kosong , tujuh pada pointer ke jenis yang berbeda), yang cukup cukup.
Seperti disebutkan di atas, beberapa kompiler (* khe-khe * ... Borland Builder 6.0 ... * khe *) tidak membedakan antara pointer ke tipe dan anggota kelas, oleh karena itu kami akan menulis cek pembantu lain untuk kasus ini sehingga kami kemudian dapat memilih implementasi 
nullptr_t yang diinginkan melalui kelas. jika dibutuhkan.
 struct _member_ptr_is_same_as_ptr { struct test {}; typedef void(test::*member_ptr_type)(void); static const bool value = _is_convertable_to_void_ptr_impl<member_ptr_type>::value; }; template<bool> struct _nullptr_t_as_class_chooser { typedef nullptr_detail::nullptr_t_as_class_impl type; }; template<> struct _nullptr_t_as_class_chooser<false> { typedef nullptr_detail::nullptr_t_as_class_impl1 type; }; 
Dan kemudian hanya tinggal memeriksa implementasi yang berbeda dari 
nullptr_t dan memilih kompiler yang sesuai untuk kompiler.
Memilih implementasi nullptr_t template<bool> struct _nullptr_choose_as_int { typedef nullptr_detail::nullptr_t_as_int type; }; template<bool> struct _nullptr_choose_as_enum { typedef nullptr_detail::nullptr_t_as_enum type; }; template<bool> struct _nullptr_choose_as_class { typedef _nullptr_t_as_class_chooser<_member_ptr_is_same_as_ptr::value>::type type; }; template<> struct _nullptr_choose_as_int<false> { typedef nullptr_detail::nullptr_t_as_void type; }; template<> struct _nullptr_choose_as_enum<false> { struct as_int { typedef nullptr_detail::nullptr_t_as_int nullptr_t_as_int; static const bool _is_convertable_to_ptr = _is_convertable_to_ptr_impl<nullptr_t_as_int>::value; static const bool _equal_void_ptr = _is_equal_size_to_void_ptr<nullptr_t_as_int>::value; }; typedef _nullptr_choose_as_int<as_int::_is_convertable_to_ptr == bool(true) && as_int::_equal_void_ptr == bool(true)>::type type; }; template<> struct _nullptr_choose_as_class<false> { struct as_enum { typedef nullptr_detail::nullptr_t_as_enum nullptr_t_as_enum; static const bool _is_convertable_to_ptr = _is_convertable_to_ptr_impl<nullptr_t_as_enum>::value; static const bool _equal_void_ptr = _is_equal_size_to_void_ptr<nullptr_t_as_enum>::value; static const bool _can_be_ct_constant = true; 
 Pertama, kami memeriksa kemungkinan mewakili 
nullptr_t sebagai kelas, tetapi karena saya tidak menemukan kompiler universal dari solusi 
independen , saya tidak menemukan objek tipe yang dapat menjadi konstanta waktu kompilasi (omong-omong, saya terbuka untuk saran mengenai subjek ini, karena kemungkinan hal ini memungkinkan), opsi ini selalu 
dicentang ( 
_can_be_ct_constant selalu 
salah ). Selanjutnya, kita beralih untuk memeriksa varian dengan view through 
enum . Jika masih tidak memungkinkan untuk ditampilkan (kompiler tidak dapat menampilkan pointer melalui 
enum atau ukurannya entah bagaimana salah), maka kami mencoba untuk menggambarkannya sebagai tipe integer (yang ukurannya akan sama dengan ukuran pointer untuk 
membatalkan ). Yah, bahkan jika ini tidak berhasil, maka kami memilih implementasi dari tipe 
nullptr_t via 
void * .
Pada titik ini, sebagian besar kekuatan SFINAE dalam kombinasi dengan templat C ++ terungkap, karena itu dimungkinkan untuk memilih implementasi yang diperlukan tanpa beralih ke makro bergantung-kompiler, dan memang ke makro (tidak seperti boost di mana semua ini akan 
dijejali dengan 
#ifdef #else # checking endif ).
Tetap hanya untuk menentukan jenis alias untuk 
nullptr_t di 
namespace stdex dan define untuk 
nullptr (untuk memenuhi persyaratan standar lain bahwa alamat 
nullptr tidak dapat diambil, serta menggunakan 
nullptr sebagai konstanta waktu kompilasi).
 namespace stdex { typedef detail::_nullptr_chooser::type nullptr_t; } #define nullptr (stdex::nullptr_t)(STDEX_NULL) 
Akhir bab ketiga. Pada 
bab keempat, akhirnya saya bisa mengetikkan type_traits dan bug apa lagi dalam kompiler yang saya temui selama pengembangan.
Terima kasih atas perhatian anda