(Saya mencari satu atau dua contoh untuk membuktikan maksudnya, bukan daftar.)
Pernahkah terjadi perubahan dalam standar C ++ (misalnya dari 98 menjadi 11, 11 menjadi 14 dll.) Mengubah perilaku kode pengguna yang ada, terbentuk dengan baik, dan berperilaku terdefinisi - secara diam-diam? yaitu tanpa peringatan atau kesalahan saat mengkompilasi dengan versi standar yang lebih baru?
Catatan:
- Saya bertanya tentang perilaku yang diamanatkan standar, bukan tentang pilihan penulis pelaksana / penyusun.
- Semakin sedikit kode yang dibuat-buat, semakin baik (sebagai jawaban atas pertanyaan ini).
- Saya tidak bermaksud kode dengan deteksi versi seperti
#if __cplusplus >= 201103L
. - Jawaban yang melibatkan model memori baik-baik saja.
c++
language-lawyer
standardization
einpoklum
sumber
sumber
auto
. Sebelum C ++ 11,auto x = ...;
deklarasikanint
. Setelah itu, ia menyatakan apapun...
itu.auto
variabel were -type. Saya pikir Anda mungkin dapat menghitung di satu sisi jumlah orang di dunia yang akan menulis kode semacam itu, kecuali untuk kontes kode C yang dikaburkan ...Jawaban:
Jenis kembalian
string::data
berubah dariconst char*
menjadichar*
dalam C ++ 17. Hal itu tentu bisa membuat perbedaanvoid func(char* data) { cout << data << " is not const\n"; } void func(const char* data) { cout << data << " is const\n"; } int main() { string s = "xyz"; func(s.data()); }
Sedikit dibuat-buat tetapi program legal ini akan mengubah keluarannya dari C ++ 14 menjadi C ++ 17.
sumber
std::string
perubahan untuk C ++ 17. Jika ada, saya akan mengira perubahan C ++ 11 mungkin telah menyebabkan perubahan perilaku diam. +1.Jawaban atas pertanyaan ini menunjukkan bagaimana menginisialisasi vektor menggunakan
size_type
nilai tunggal dapat menghasilkan perilaku yang berbeda antara C ++ 03 dan C ++ 11.std::vector<Something> s(10);
C ++ 03 default-membangun objek sementara dari tipe elemen
Something
dan menyalin-konstruksi setiap elemen dalam vektor dari sementara itu.C ++ 11 default-membangun setiap elemen dalam vektor.
Dalam banyak kasus (kebanyakan?), Ini menghasilkan keadaan akhir yang setara, tetapi tidak ada alasan mereka harus melakukannya. Itu tergantung pada implementasi
Something
konstruktor default / copy.Lihat contoh yang dibuat-buat ini :
class Something { private: static int counter; public: Something() : v(counter++) { std::cout << "default " << v << '\n'; } Something(Something const & other) : v(counter++) { std::cout << "copy " << other.v << " to " << v << '\n'; } ~Something() { std::cout << "dtor " << v << '\n'; } private: int v; }; int Something::counter = 0;
C ++ 03 akan secara default membangun satu
Something
denganv == 0
kemudian menyalin-membangun sepuluh lagi dari yang satu itu. Pada akhirnya, vektor berisi sepuluh objek yangv
nilainya 1 sampai 10, inklusif.C ++ 11 akan membangun default setiap elemen. Tidak ada salinan yang dibuat. Pada akhirnya, vektor berisi sepuluh objek yang
v
nilainya 0 sampai 9, inklusif.sumber
cv::mat
. Konstruktor default mengalokasikan memori baru, sedangkan konstruktor salinan membuat tampilan baru ke memori yang ada.Standar memiliki daftar perubahan yang melanggar dalam Lampiran C [diff] . Banyak dari perubahan ini dapat menyebabkan perubahan perilaku diam.
Sebuah contoh:
int f(const char*); // #1 int f(bool); // #2 int x = f(u8"foo"); // until C++20: calls #1; since C++20: calls #2
sumber
bool
versi tersebut bukanlah perubahan yang dimaksudkan, hanya efek samping dari aturan konversi lainnya. Maksud sebenarnya adalah untuk menghentikan beberapa kebingungan antara pengkodean karakter, perubahan sebenarnya adalahu8
literal dulu membericonst char*
tetapi sekarang membericonst char8_t*
.Setiap kali mereka menambahkan metode baru (dan sering kali berfungsi) ke pustaka standar, hal ini terjadi.
Misalkan Anda memiliki tipe pustaka standar:
struct example { void do_stuff() const; };
cukup mudah. Dalam beberapa revisi standar, metode baru atau kelebihan beban atau apa pun ditambahkan:
struct example { void do_stuff() const; void method(); // a new method };
ini diam-diam dapat mengubah perilaku program C ++ yang ada.
Ini karena kemampuan refleksi C ++ yang saat ini terbatas cukup untuk mendeteksi jika ada metode seperti itu, dan menjalankan kode yang berbeda berdasarkan padanya.
template<class T, class=void> struct detect_new_method : std::false_type {}; template<class T> struct detect_new_method< T, std::void_t< decltype( &T::method ) > > : std::true_type {};
ini hanyalah cara yang relatif sederhana untuk mendeteksi yang baru
method
, ada banyak cara.void task( std::false_type ) { std::cout << "old code"; }; void task( std::true_type ) { std::cout << "new code"; }; int main() { task( detect_new_method<example>{} ); }
Hal yang sama bisa terjadi saat Anda menghapus metode dari kelas.
Meskipun contoh ini secara langsung mendeteksi keberadaan sebuah metode, hal semacam ini yang terjadi secara tidak langsung bisa jadi kurang dibuat-buat. Sebagai contoh konkret, Anda mungkin memiliki mesin serialisasi yang memutuskan apakah sesuatu dapat diserialkan sebagai wadah berdasarkan apakah iterable, atau jika memiliki data yang menunjuk-ke-mentah-byte dan ukuran anggota, dengan satu lebih disukai daripada yang lain.
Standar pergi dan menambahkan
.data()
metode ke wadah, dan tiba-tiba perubahan jenis jalur yang digunakan untuk serialisasi.Semua standar C ++ dapat melakukannya, jika tidak ingin dibekukan, adalah membuat jenis kode yang diam-diam rusak menjadi langka atau entah bagaimana tidak masuk akal.
sumber
Oh boy ... Link cpplearner disediakan adalah menakutkan .
Antara lain, C ++ 20 melarang deklarasi struct C-style dari C ++ struct.
typedef struct { void member_foo(); // Ill-formed since C++20 } m_struct;
Jika Anda diajari menulis struktur seperti itu (dan orang-orang yang mengajar "C dengan kelas" mengajarkan persis seperti itu) Anda kacau .
sumber
typedef
struct saya, dan saya pasti tidak akan menyia-nyiakan kapur saya untuk itu. Ini pasti masalah selera, dan meskipun ada orang yang sangat berpengaruh (Torvalds ...) yang memiliki pandangan yang sama dengan Anda, orang lain seperti saya akan menunjukkan, bahwa hanya diperlukan konvensi penamaan untuk jenis. Mengacaukan kode denganstruct
kata kunci menambah sedikit pemahaman bahwa huruf kapital (MyClass* object = myClass_create();
) tidak akan menyampaikan. Saya menghormati jika Anda menginginkanstruct
kode Anda. Tapi aku tidak ingin itu ada di dalam diriku.struct
hanya untuk tipe data biasa-lama, danclass
apa pun yang memiliki fungsi anggota. Tetapi Anda tidak dapat menggunakan konvensi itu di C karena tidak adaclass
di C.struct
sebenarnya POD. Cara saya menulis kode C, kebanyakan struktur hanya tersentuh oleh kode dalam satu file dan oleh fungsi yang membawa nama kelasnya. Ini pada dasarnya OOP tanpa gula sintaksis. Ini memungkinkan saya untuk benar-benar mengontrol perubahan apa di dalam astruct
, dan invarian mana yang dijamin di antara anggotanya. Jadi, sayastructs
cenderung memiliki fungsi anggota, implementasi pribadi, invarian, dan abstrak dari anggota data mereka. Tidak terdengar seperti POD, bukan?extern "C"
blok, saya tidak melihat ada masalah dengan perubahan ini. Tidak ada yang harus mengetikkan struct dalam C ++. Ini bukan rintangan yang lebih besar daripada kenyataan bahwa C ++ memiliki semantik yang berbeda dari Java. Saat Anda mempelajari bahasa pemrograman baru, Anda mungkin perlu mempelajari beberapa kebiasaan baru.Berikut adalah contoh yang mencetak 3 di C ++ 03 tetapi 0 di C ++ 11:
template<int I> struct X { static int const c = 2; }; template<> struct X<0> { typedef int c; }; template<class T> struct Y { static int const c = 3; }; static int const c = 4; int main() { std::cout << (Y<X< 1>>::c >::c>::c) << '\n'; }
Perubahan perilaku ini disebabkan oleh penanganan khusus untuk
>>
. Sebelum C ++ 11,>>
selalu menjadi operator shift kanan. Dengan C ++ 11,>>
dapat menjadi bagian dari deklarasi template juga.sumber
>>
cara itu.Trigraf jatuh
File sumber dikodekan dalam himpunan karakter fisik yang dipetakan dalam cara implementasi yang ditentukan ke himpunan karakter sumber , yang ditentukan dalam standar. Untuk mengakomodasi pemetaan dari beberapa kumpulan karakter fisik yang tidak memiliki semua tanda baca yang diperlukan oleh kumpulan karakter sumber, bahasa menentukan trigraf — urutan tiga karakter umum yang dapat digunakan sebagai pengganti karakter tanda baca yang kurang umum. Preprocessor dan compiler diperlukan untuk menangani ini.
Di C ++ 17, trigraf telah dihapus. Jadi beberapa file sumber tidak akan diterima oleh kompiler yang lebih baru kecuali mereka diterjemahkan pertama kali dari kumpulan karakter fisik ke beberapa kumpulan karakter fisik lainnya yang memetakan satu-ke-satu ke kumpulan karakter sumber. (Dalam praktiknya, sebagian besar kompiler hanya membuat interpretasi trigraf opsional.) Ini bukan perubahan perilaku halus, tetapi perubahan yang merusak mencegah file sumber yang sebelumnya dapat diterima untuk dikompilasi tanpa proses terjemahan eksternal.
Lebih banyak kendala
char
Standar juga mengacu pada himpunan karakter eksekusi , yang didefinisikan implementasi, tetapi harus berisi setidaknya seluruh himpunan karakter sumber ditambah sejumlah kecil kode kontrol.
Standar C ++ didefinisikan
char
sebagai tipe integral yang mungkin tidak bertanda tangan yang dapat secara efisien mewakili setiap nilai dalam set karakter eksekusi. Dengan representasi dari seorang pengacara bahasa, Anda dapat berargumen bahwa achar
harus minimal 8 bit.Jika implementasi Anda menggunakan nilai unsigned untuk
char
, maka Anda tahu itu dapat berkisar dari 0 hingga 255, dan karenanya cocok untuk menyimpan setiap nilai byte yang mungkin.Tetapi jika implementasi Anda menggunakan nilai yang ditandatangani, itu memiliki opsi.
Sebagian besar akan menggunakan komplemen dua, memberikan
char
kisaran minimum -128 hingga 127. Itu berarti 256 nilai unik.Tetapi opsi lain adalah tanda + magnitudo, di mana satu bit dicadangkan untuk menunjukkan apakah bilangan tersebut negatif dan tujuh bit lainnya menunjukkan besarnya. Itu akan memberikan
char
kisaran -127 hingga 127, yang hanya 255 nilai unik. (Karena Anda kehilangan satu kombinasi bit yang berguna untuk mewakili -0.)Saya tidak yakin panitia pernah secara eksplisit menetapkan ini sebagai cacat, tetapi itu karena Anda tidak dapat mengandalkan standar untuk menjamin perjalanan pulang pergi dari dan
unsigned char
kechar
belakang akan mempertahankan nilai aslinya. (Dalam praktiknya, semua implementasi melakukannya karena mereka semua menggunakan dua komplemen untuk tipe integral bertanda tangan.)Baru-baru ini (C ++ 17?) Kata-katanya diperbaiki untuk memastikan perjalanan bolak-balik. Perbaikan itu, bersama dengan semua persyaratan lainnya
char
, secara efektif mengamanatkan pelengkap dua untuk ditandatanganichar
tanpa mengatakan secara eksplisit (bahkan ketika standar terus memungkinkan representasi tanda + besaran untuk jenis integral bertanda tangan lainnya). Ada proposal yang meminta semua tipe integral bertanda tangan menggunakan komplemen dua, tapi saya tidak ingat apakah itu berhasil menjadi C ++ 20.Jadi satu ini adalah semacam kebalikan dari apa yang Anda cari karena memberikan sebelumnya
yang salahterlalu lancang kode memperbaiki berlaku surut.sumber
Saya tidak yakin apakah Anda akan menganggap ini sebagai perubahan yang melanggar ke kode yang benar, tapi ...
Sebelum C ++ 11, penyusun diizinkan, tetapi tidak diharuskan, untuk menghilangkan salinan dalam keadaan tertentu, bahkan ketika pembuat salinan memiliki efek samping yang dapat diamati. Sekarang kami telah menjamin penghapusan salinan. Perilaku pada dasarnya berubah dari yang ditentukan oleh implementasi menjadi diperlukan.
Ini berarti bahwa efek samping konstruktor salinan Anda mungkin terjadi dengan versi yang lebih lama, tetapi tidak akan pernah terjadi dengan yang lebih baru. Anda dapat berargumen bahwa kode yang benar seharusnya tidak bergantung pada hasil yang ditentukan implementasi, tetapi menurut saya itu tidak sama dengan mengatakan kode seperti itu tidak benar.
sumber
Perilaku saat membaca (numerik) data dari aliran, dan gagal membaca, diubah sejak c ++ 11.
Misalnya, membaca integer dari aliran, sementara itu tidak mengandung integer:
#include <iostream> #include <sstream> int main(int, char **) { int a = 12345; std::string s = "abcd"; // not an integer, so will fail std::stringstream ss(s); ss >> a; std::cout << "fail = " << ss.fail() << " a = " << a << std::endl; // since c++11: a == 0, before a still 12345 }
Karena c ++ 11 akan menyetel integer baca ke 0 jika gagal; di c ++ <11 integer tidak berubah. Meskipun demikian, gcc, meskipun memaksa standar kembali ke c ++ 98 (dengan -std = c ++ 98) selalu menampilkan perilaku baru setidaknya sejak versi 4.4.7.
(Imho perilaku lama sebenarnya lebih baik: mengapa mengubah nilai menjadi 0, yang dengan sendirinya valid, ketika tidak ada yang bisa dibaca?)
Referensi: lihat https://en.cppreference.com/w/cpp/locale/num_get/get
sumber