Mencapai kompatibilitas kedepan dengan C ++ 11

12

Saya bekerja pada aplikasi perangkat lunak besar yang harus dijalankan pada beberapa platform. Beberapa platform ini mendukung beberapa fitur C ++ 11 (mis. MSVS 2010) dan beberapa tidak mendukung apa pun (mis. GCC 4.3.x). Saya berharap situasi ini akan berlanjut selama beberapa tahun (tebakan terbaik saya: 3-5 tahun).

Mengingat itu, saya ingin mengatur antarmuka kompatibilitas sehingga (untuk tingkat apa pun yang mungkin) orang dapat menulis kode C ++ 11 yang masih akan dikompilasi dengan kompiler yang lebih tua dengan pemeliharaan minimum. Secara keseluruhan, tujuannya adalah untuk meminimalkan # ifdef sebanyak mungkin yang wajar sambil tetap mengaktifkan sintaks / fitur dasar C ++ 11 pada platform yang mendukungnya, dan memberikan emulasi pada platform yang tidak.

Mari kita mulai dengan std :: move (). Cara paling jelas untuk mencapai kompatibilitas adalah dengan meletakkan sesuatu seperti ini di file header umum:

#if !defined(HAS_STD_MOVE)
namespace std { // C++11 emulation
  template <typename T> inline T& move(T& v) { return v; }
  template <typename T> inline const T& move(const T& v) { return v; }
}
#endif // !defined(HAS_STD_MOVE)

Ini memungkinkan orang untuk menulis hal-hal seperti

std::vector<Thing> x = std::move(y);

... dengan impunitas. Ia melakukan apa yang mereka inginkan dalam C ++ 11 dan melakukan yang terbaik yang dapat dilakukan dalam C ++ 03. Ketika kami akhirnya menjatuhkan kompiler C ++ 03 yang terakhir, kode ini dapat tetap apa adanya.

Namun, menurut standar, adalah ilegal untuk menyuntikkan simbol baru ke stdnamespace. Itulah teorinya. Pertanyaan saya adalah: secara praktis apakah ada salahnya melakukan ini sebagai cara untuk mencapai kompatibilitas ke depan?

mcmcc
sumber
1
Boost sudah menyediakan sedikit hal ini, dan sudah memiliki kode untuk menggunakan fitur baru ketika / di mana tersedia, jadi Anda mungkin bisa menggunakan apa yang disediakan Boost, dan selesai melakukannya. Tentu saja, ada batasan - sebagian besar fitur baru ditambahkan secara khusus karena solusi berbasis perpustakaan tidak memadai.
Jerry Coffin
ya, saya berpikir secara khusus tentang fitur-fitur yang dapat diimplementasikan di tingkat perpustakaan, bukan perubahan sintaksis. Boost tidak benar-benar mengatasi masalah kompatibilitas ke depan (mulus). Kecuali saya melewatkan sesuatu ...
mcmcc
Gcc 4.3 sudah memiliki beberapa fitur C ++ 11 yang bagus, Rvalue-reference mungkin yang paling penting.
Jan Hudec
@ JanHudec: Anda benar. Contoh yang buruk. Dalam kasus apa pun, ada kompiler lain yang pasti tidak mendukung sintaksis (mis. Versi apa pun dari kompiler C ++ IBM yang kami miliki).
mcmcc

Jawaban:

9

Saya telah bekerja untuk sementara dalam menjaga tingkat kompatibilitas ke depan dan ke belakang dalam program C ++ saya, sampai akhirnya saya harus membuat toolkit perpustakaan dari itu , yang saya siapkan untuk rilis sudah dirilis. Secara umum, selama Anda menerima bahwa Anda tidak akan mendapatkan kompatibilitas ke depan "sempurna" baik dalam fitur (beberapa hal tidak dapat ditiru) tidak dalam sintaks (Anda mungkin harus menggunakan makro, ruang nama alternatif untuk beberapa hal) maka Anda sudah siap.

Ada banyak fitur bagus yang dapat ditiru di C ++ 03 pada level yang cukup untuk penggunaan praktis - dan tanpa semua kerumitan yang datang dengan mis .: Boost. Heck, bahkan proposal standar C ++ untuk nullptrmenyarankan backport C ++ 03. Dan kemudian ada TR1 misalnya untuk semuanya C ++ 11-tetapi-kita-sudah-pratinjau-selama-tahun hal. Tidak hanya itu, beberapa fitur C ++ 14 seperti varian tegas, functors transparan dan optional dapat diimplementasikan di C ++ 03!

Hanya dua hal yang saya tahu yang tidak dapat sepenuhnya didukung adalah template constexpr dan variadic.

Berkenaan dengan seluruh masalah menambahkan barang ke namespace std, pandangan saya tentang hal itu adalah tidak masalah - sama sekali. Pikirkan Boost, salah satu pustaka C ++ yang paling penting dan relevan, dan implementasinya TR1: Boost.Tr1. Jika Anda ingin meningkatkan C ++, buat itu maju kompatibel dengan C ++ 11, maka menurut definisi Anda mengubahnya menjadi sesuatu yang bukan C ++ 03, jadi memblokir diri Anda sendiri atas Standar yang ingin Anda hindari atau tinggalkan. , sederhananya, kontraproduktif. Puritan akan mengeluh, tetapi menurut definisi orang tidak perlu peduli tentang mereka.

Tentu saja, hanya karena Anda tidak akan mengikuti (03) Standar setelah semua tidak berarti Anda tidak dapat mencoba, atau akan dengan senang hati berkeliling melanggar itu. Itu bukan intinya. Selama Anda tetap sangat berhati-hati mengontrol apa yang ditambahkan ke stdnamespace, dan memiliki kontrol terhadap lingkungan tempat perangkat lunak Anda digunakan (mis .: lakukan pengujian!), Seharusnya tidak ada bahaya yang tidak dapat dilanggar sama sekali. Jika memungkinkan, tentukan semuanya dalam namespace yang terpisah dan hanya tambahkan usingarahan ke namespace stdsehingga Anda tidak menambahkan apa pun di luar apa yang "mutlak" perlu masukkan. Yang, IINM, kurang lebih seperti yang dilakukan Boost.TR1.


Pembaruan (2013) : sebagai permintaan pertanyaan asli dan melihat beberapa komentar yang tidak dapat saya tambahkan karena kurangnya rep, berikut adalah daftar fitur C ++ 11 dan C ++ 14 dan tingkat portabilitasnya ke C ++ 03:

  • nullptr: sepenuhnya dapat dilaksanakan mengingat dukungan resmi Komite; Anda mungkin harus memberikan beberapa spesialisasi type_traits juga sehingga dikenali sebagai tipe "asli".
  • forward_list: sepenuhnya dapat diterapkan, meskipun dukungan pengalokasi bergantung pada apa yang dapat diberikan oleh implementasi Tr1 Anda.
  • Algoritma baru (partition_copy, dll): sepenuhnya dapat diterapkan.
  • Konstruksi kontainer dari brace-sequence (mis . vector<int> v = {1, 2, 3, 4};: .:) : sepenuhnya dapat diterapkan, meskipun lebih banyak daripada yang diinginkan.
  • static_assert: hampir-sepenuhnya diimplementasikan ketika diimplementasikan sebagai makro (Anda hanya harus berhati-hati dengan koma).
  • unique_ptr: hampir-sepenuhnya dapat diterapkan, tetapi Anda juga akan memerlukan dukungan dari kode panggilan (untuk menyimpannya dalam wadah, dll); lihat di bawah ini sekalipun.
  • rvalue-reference: hampir dapat diimplementasikan sepenuhnya tergantung pada berapa banyak yang Anda harapkan dari mereka (misalnya: Boost Move).
  • Iterasi Foreach: hampir sepenuhnya dapat diimplementasikan, sintaks akan sedikit berbeda.
  • menggunakan fungsi lokal sebagai argumen (misalnya transformasi:): hampir sepenuhnya dapat diimplementasikan, tetapi sintaksis akan cukup berbeda - misalnya, fungsi lokal tidak didefinisikan di situs panggilan tetapi tepat sebelumnya.
  • operator konversi eksplisit: dapat diterapkan ke level praktis (membuat konversi dibuat eksplisit), lihat "eksplisit_cast" Imperfect C ++ ; tetapi integrasi dengan fitur bahasa seperti static_cast<>mungkin hampir mustahil.
  • penerusan argumen: dapat diterapkan ke tingkat praktis yang diberikan di atas pada rvalue-referensi, tetapi Anda harus memberikan N kelebihan pada fungsi Anda mengambil argumen yang dapat diteruskan.
  • move: dapat diterapkan ke level praktis (lihat dua hal di atas). Tentu saja, Anda harus menggunakan wadah pengubah dan benda untuk mendapatkan keuntungan dari ini.
  • Alokasi pengalokasi: Tidak benar-benar dapat diimplementasikan kecuali implementasi Tr1 Anda dapat membantunya.
  • tipe karakter multibyte: Tidak benar-benar dapat diterapkan kecuali Tr1 Anda dapat mendukung Anda. Tetapi untuk tujuan yang dimaksud lebih baik mengandalkan perpustakaan yang dirancang khusus untuk menangani masalah ini, seperti ICU, bahkan jika menggunakan C ++ 11.
  • Daftar argumen variadik: dapat diterapkan dengan sedikit kerumitan, perhatikan penerusan argumen.
  • noexcept: tergantung pada fitur kompiler Anda.
  • New autosemantik dan decltype: tergantung pada fitur compiler Anda - misalnya .: __typeof__.
  • tipe integer berukuran ( int16_t, dll): tergantung pada fitur kompiler Anda - atau Anda dapat mendelegasikan ke Portable stdint.h.
  • ketik atribut: tergantung pada fitur kompiler Anda.
  • Daftar Initializer: Tidak dapat diterapkan untuk pengetahuan saya; namun jika yang Anda inginkan adalah menginisialisasi kontainer dengan urutan, lihat di atas pada "konstruksi kontainer".
  • Aliasing Templat: Tidak dapat diaplikasikan pada pengetahuan saya, tetapi itu adalah fitur yang tidak dibutuhkan, dan kami sudah memiliki ::typetemplat selamanya
  • Templat variadik: Tidak dapat diterapkan untuk pengetahuan saya; penutupnya adalah argumen templat defaulting, yang membutuhkan N spesialisasi, dll.
  • constexpr: Tidak dapat diterapkan untuk pengetahuan saya.
  • Inisialisasi seragam: Tidak dapat diterapkan untuk pengetahuan saya, tetapi inisialisasi default -constructor yang dijamin dapat diimplementasikan dengan nilai Boost yang diinisialisasi.
  • C ++ 14 dynarray: sepenuhnya dapat diterapkan.
  • C ++ 14 optional<>: hampir sepenuhnya diimplementasikan asalkan kompiler C ++ 03 Anda mendukung pengaturan perataan.
  • C ++ 14 functors transparan: hampir sepenuhnya diimplementasikan, tetapi kode klien Anda mungkin harus secara eksplisit menggunakan mis .: std::less<void>untuk membuatnya berfungsi.
  • C ++ 14 varian menegaskan baru (seperti assure): sepenuhnya dapat diterapkan jika Anda ingin menegaskan, hampir sepenuhnya dapat diterapkan jika Anda ingin mengaktifkan lemparan sebagai gantinya.
  • C ++ 14 ekstensi tuple (dapatkan elemen tuple berdasarkan tipe): sepenuhnya dapat diterapkan, dan Anda bahkan bisa membuatnya gagal untuk dikompilasi dengan kasus-kasus persis yang dijelaskan dalam proposal fitur.

(Penafian: beberapa fitur ini diimplementasikan di pustaka C ++ backports saya yang saya tautkan di atas, jadi saya pikir saya tahu apa yang saya bicarakan ketika saya mengatakan "sepenuhnya" atau "hampir sepenuhnya".)

Luis Machuca
sumber
6

Ini pada dasarnya tidak mungkin. Pertimbangkan std::unique_ptr<Thing>. Jika mungkin untuk meniru referensi nilai sebagai perpustakaan, itu tidak akan menjadi fitur bahasa.

DeadMG
sumber
1
Saya memang mengatakan "sedapat mungkin". Jelas beberapa fitur harus ditinggalkan #defd atau tidak digunakan sama sekali. std :: move () kebetulan salah satu yang Anda dapat mendukung sintaks (walaupun bukan fungsionalitas).
mcmcc
2
Sebenarnya proposal referensi nilai menyebutkan solusi berbasis perpustakaan!
Jan Hudec
Lebih khusus lagi, adalah mungkin untuk mengimplementasikan pemindahan semantik untuk kelas tertentu dalam C ++ 03, jadi harus dimungkinkan untuk mendefinisikan di std::unique_ptrsana, tetapi beberapa fitur referensi rvalue lainnya tidak dapat diimplementasikan dalam C ++ 03, jadi std::forwardtidak mungkin. Hal lain adalah bahwa std::unique_ptritu tidak akan berguna, karena koleksi tidak akan menggunakan semantik langkah kecuali Anda mengganti semuanya.
Jan Hudec
@ JanHudec: Tidak mungkin untuk mendefinisikan unique_ptr. Lihatlah kegagalan auto_ptr. unique_ptrsecara praktis adalah contoh buku teks dari kelas yang semantiknya secara fundamental diaktifkan oleh fitur bahasa.
DeadMG
@DeadMG: Tidak, itu tidak unique_ptrsecara fundamental diaktifkan oleh fitur bahasa. Ini tidak akan sangat berguna tanpa fitur itu. karena tanpa penerusan sempurna tidak akan dapat digunakan dalam banyak kasus dan penerusan sempurna memang memerlukan fitur itu.
Jan Hudec
2
  1. Gcc mulai memperkenalkan C ++ 11 (masih C ++ 0x pada waktu itu) di 4.3. Tabel ini mengatakan sudah memiliki referensi nilai dan beberapa fitur lain yang kurang digunakan (Anda harus menentukan -std=c++0xopsi untuk mengaktifkannya).
  2. Banyak tambahan ke pustaka standar di C ++ 11 sudah didefinisikan di TR1 dan GNU stdlibc ++ menyediakannya di std :: tr1 namespace. Jadi lakukan saja penggunaan kondisional yang sesuai.
  3. Boost mendefinisikan sebagian besar fungsi TR1 dan dapat menyuntikkannya ke namespace TR1 jika Anda tidak memilikinya (tetapi VS2010 melakukannya dan gcc 4.3 berfungsi dengan baik jika Anda menggunakan GNU stdlibc ++).
  4. Menempatkan apa pun di stdnamespace adalah "perilaku tidak terdefinisi". Itu artinya spesifikasinya tidak mengatakan apa yang akan terjadi. Tetapi jika Anda tahu bahwa pada platform tertentu perpustakaan standar tidak mendefinisikan sesuatu, langsung saja dan definisikan. Hanya berharap bahwa Anda harus memeriksa pada setiap platform apa yang Anda butuhkan dan apa yang dapat Anda tetapkan.
  5. The Proposal untuk referensi nilai p, N1690 menyebutkan bagaimana menerapkan langkah semantik dalam C ++ 03. Itu bisa digunakan sebagai pengganti unique_ptr. Namun, itu tidak akan terlalu berguna, karena bergantung pada koleksi yang sebenarnya menggunakan semantik bergerak dan yang C ++ 03 jelas tidak akan.
Jan Hudec
sumber
1
Anda benar tentang GCC tetapi sayangnya, saya juga harus mendukung kompiler (non-GCC) lainnya. Peluru # 4 Anda adalah inti dari pertanyaan yang saya ajukan. # 5 menarik tetapi saya tidak mencari untuk mendukung semantik bergerak (optimasi salinan) pada platform yang lebih tua ini melainkan hanya "std :: move ()" sebagai sintaks yang dapat dikompilasi.
mcmcc