Bisakah kode C ++ valid di C ++ 03 dan C ++ 11 tetapi melakukan hal yang berbeda?

299

Apakah mungkin untuk kode C ++ sesuai dengan standar C ++ 03 dan standar C ++ 11 , tetapi melakukan hal yang berbeda tergantung pada standar mana yang sedang dikompilasi?

Erik Sjölund
sumber
26
Saya cukup yakin autodapat menghasilkan situasi seperti ini
OMGtechy
8
Iya. Salah satu contohnya adalah >>ketika digunakan dalam templat. Anda dapat menemukan situasi di mana ia dapat dikompilasi untuk kedua standar. Satu lagi yang saya yakin akan mudah untuk menemukan perubahan adalah dalam inisialisasi.
chris
5
Inilah artikel yang bagus tentang situasi >>: gustedt.wordpress.com/2013/12/15/…
chris
6
@ TechGtechy: Saya pikir tidak autobisa menyebabkan ini. Dengan makna lama, autodeklarasi membutuhkan nama jenis; dengan makna baru, nama tipe tidak diizinkan.
Keith Thompson
2
Bagaimana itu terbuka? Anda sendiri menunjukkan melalui pertanyaan lain bahwa jawaban untuk pertanyaan ini adalah "ya, berikut adalah contoh caranya". Ada jawaban yang sangat pasti untuk pertanyaan itu, seperti yang Anda tunjukkan.
Jalf

Jawaban:

284

Jawabannya pasti ya. Di sisi positifnya ada:

  • Kode yang sebelumnya disalin objek secara implisit sekarang akan memindahkannya jika memungkinkan.

Di sisi negatif, beberapa contoh tercantum dalam lampiran C standar. Meskipun ada lebih banyak yang negatif daripada positif, masing-masing dari mereka jauh lebih kecil kemungkinannya untuk terjadi.

Literal string

#define u8 "abc"
const char* s = u8"def"; // Previously "abcdef", now "def"

dan

#define _x "there"
"hello "_x // Previously "hello there", now a user defined string literal

Ketik konversi 0

Dalam C ++ 11, hanya literal yang merupakan konstanta penunjuk integer nol:

void f(void *); // #1
void f(...); // #2
template<int N> void g() {
    f(0*N); // Calls #2; used to call #1
}

Hasil bulat setelah pembagian bilangan bulat dan modulo

Dalam C ++ 03 kompiler diizinkan untuk membulatkan ke 0 atau menuju infinity negatif. Dalam C ++ 11 adalah wajib untuk membulatkan ke 0

int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0

Spasi putih antara kurung penutup templat bersarang >> vs>>

Di dalam spesialisasi atau instantiasi yang >>mungkin ditafsirkan sebagai pergeseran kanan dalam C ++ 03. Ini lebih mungkin untuk memecahkan kode yang ada: (dari http://gustedt.wordpress.com/2013/12/15/a-disimprovement-observed-from-the-outside-right-angle-brackets/ )

template< unsigned len > unsigned int fun(unsigned int x);
typedef unsigned int (*fun_t)(unsigned int);
template< fun_t f > unsigned int fon(unsigned int x);

void total(void) {
    // fon<fun<9> >(1) >> 2 in both standards
    unsigned int A = fon< fun< 9 > >(1) >>(2);
    // fon<fun<4> >(2) in C++03
    // Compile time error in C++11
    unsigned int B = fon< fun< 9 >>(1) > >(2);
}

Operator newsekarang dapat melempar pengecualian lain selainstd::bad_alloc

struct foo { void *operator new(size_t x){ throw std::exception(); } }
try {
    foo *f = new foo();
} catch (std::bad_alloc &) {
    // c++03 code
} catch (std::exception &) {
    // c++11 code
}

Destruktor yang dideklarasikan oleh pengguna memiliki contoh spesifikasi pengecualian implisit dari Perubahan apa yang diperkenalkan di C ++ 11?

struct A {
    ~A() { throw "foo"; } // Calls std::terminate in C++11
};
//...
try { 
    A a; 
} catch(...) { 
    // C++03 will catch the exception
} 

size() kontainer sekarang diperlukan untuk berjalan di O (1)

std::list<double> list;
// ...
size_t s = list.size(); // Might be an O(n) operation in C++03

std::ios_base::failuretidak berasal langsung dari std::exceptionlagi

Sementara kelas dasar langsung adalah baru, std::runtime_errortidak. Jadi:

try {
    std::cin >> variable; // exceptions enabled, and error here
} catch(std::runtime_error &) {
    std::cerr << "C++11\n";
} catch(std::ios_base::failure &) {
    std::cerr << "Pre-C++11\n";
}
81%
sumber
11
Bagus, +1. Yang lain adalah bahwa pengguna menyatakan destruktor sekarang secara implisit noexecpt(true)sehingga throwdalam destruktor sekarang akan memanggil std::terminate. Tapi saya harap siapa pun yang menulis kode seperti itu akan senang dengan ini!
typ1232
4
Tapi std :: system_error itu sendiri (secara tidak langsung) berasal dari std :: exception, jadi catch (std::exception &)masih menangkap std::ios_base::failure.
user2665887
@ user2665887 Anda benar. masih dapat mempengaruhi perilaku program, tetapi saya tidak bisa memikirkan contoh minimal saat ini.
contoh
4
Saya sangat bingung, seperti yang Anda katakan operator newakurat (sekarang bisa melempar std::bad_array_new_length), tetapi contoh Anda tidak menunjukkan itu sama sekali. Kode yang Anda tampilkan adalah sama dalam C ++ 03 dan C ++ 11 AFAIK.
Mooing Duck
2
Sisi lain daftar :: size being O (1) adalah splice sekarang O (n)
Tony Delroy
56

Saya mengarahkan Anda ke artikel ini dan tindak lanjutnya , yang memiliki contoh yang bagus tentang bagaimana >>dapat mengubah makna dari C ++ 03 ke C ++ 11 sambil tetap mengkompilasi keduanya.

bool const one = true;
int const two = 2;
int const three = 3;

template<int> struct fun {
    typedef int two;
};

template<class T> struct fon {
    static int const three = ::three;
    static bool const one = ::one;
};

int main(void) {
    fon< fun< 1 >>::three >::two >::one; // valid for both  
}

Bagian kuncinya adalah baris dalam main, yang merupakan ekspresi.

Di C ++ 03:

1 >> ::three = 0
=> fon< fun< 0 >::two >::one;

fun< 0 >::two = int
=> fon< int >::one

fon< int >::one = true
=> true

Dalam C ++ 11

fun< 1 > is a type argument to fon
fon< fun<1> >::three = 3
=> 3 > ::two > ::one

::two is 2 and ::one is 1
=> 3 > 2 > 1
=> (3 > 2) > 1
=> true > 1
=> 1 > 1
=> false

Selamat, dua hasil berbeda untuk ekspresi yang sama. Memang, C + + 03 yang muncul dengan bentuk peringatan Dentang ketika saya mengujinya.

chris
sumber
itu adalah aneh bahwa hal itu tidak memerlukan typenameuntuk ::twodi C ++ 03 versi
zahir
3
Bagus, membuatnya untuk mengevaluasi trueatau falseuntuk standar yang berbeda. Mungkin kita bisa menggunakannya sebagai uji fitur </joke>
cmaster - mengembalikan monica
@ Zahir, Ini bukan tipe, hanya nilai.
chris
baik, opsi cmdline yang tepat memperingatkan tentang hal ini ( warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]), tetapi masih merupakan contoh yang bagus tentang bagaimana ::operator yang ambigu mengubah makna (baik merujuk ke lingkup global atau mereferensikan yang berdiri langsung sebelum itu)
contoh
@contoh, Cukup mengejutkan, GCC memberikan peringatan itu, tapi Dentang tidak.
chris
39

Ya, ada sejumlah perubahan yang akan menyebabkan kode yang sama menghasilkan perilaku yang berbeda antara C ++ 03 dan C ++ 11. Perbedaan aturan sekuensing membuat beberapa perubahan menarik termasuk beberapa perilaku yang sebelumnya tidak terdefinisi menjadi didefinisikan dengan baik.

1. beberapa mutasi dari variabel yang sama dalam daftar penginisialisasi

Satu kasus sudut yang sangat menarik adalah banyak mutasi dari variabel yang sama dalam daftar penginisialisasi, misalnya:

int main()
{
    int count = 0 ;
    int arrInt[2] = { count++, count++ } ;

    return 0 ;
}

Baik dalam C ++ 03 dan C ++ 11 ini didefinisikan dengan baik tetapi urutan evaluasi dalam C ++ 03 tidak ditentukan tetapi dalam C ++ 11 mereka dievaluasi dalam urutan di mana mereka muncul . Jadi jika kita mengkompilasi menggunakan clangdalam mode C ++ 03 itu memberikan peringatan berikut ( lihat langsung ):

warning: multiple unsequenced modifications to 'count' [-Wunsequenced]

    int arrInt[2] = { count++, count++ } ;

                           ^        ~~

tetapi tidak memberikan peringatan di C ++ 11 ( lihat langsung ).

2. Aturan sekuensing baru membuat i = ++ i +1; didefinisikan dengan baik dalam C ++ 11

Aturan pengurutan baru yang diadopsi setelah C ++ 03 berarti bahwa:

int i = 0 ;
i = ++ i + 1;

tidak lagi perilaku tidak terdefinisi dalam C ++ 11, ini tercakup dalam laporan cacat 637. Aturan pengurutan dan contoh tidak setuju

3. Aturan pengurutan baru juga membuat ++++ i; didefinisikan dengan baik dalam C ++ 11

Aturan pengurutan baru yang diadopsi setelah C ++ 03 berarti bahwa:

int i = 0 ;
++++i ;

tidak lagi perilaku tidak terdefinisi dalam C ++ 11.

4. Sedikit Lebih Masuk Akal Tertanda-Kiri

Konsep selanjutnya dari C ++ 11 termasuk N3485yang saya tautkan di bawah ini memperbaiki perilaku yang tidak terdefinisi yaitu menggeser 1 bit menjadi atau melewati bit tanda . Ini juga tercakup dalam laporan cacat 1457 . Howard Hinnant mengomentari pentingnya perubahan pada utas tentang Apakah kiri-bergeser (<<) bilangan bulat negatif perilaku tidak terdefinisi dalam C ++ 11? .

5. fungsi constexpr dapat diperlakukan sebagai kompilasi ekspresi konstanta waktu dalam C ++ 11

C ++ 11 memperkenalkan fungsi constexpr yang:

Specifier constexpr menyatakan bahwa adalah mungkin untuk mengevaluasi nilai fungsi atau variabel pada waktu kompilasi. Variabel dan fungsi tersebut kemudian dapat digunakan di mana hanya kompilasi ekspresi konstan waktu yang diizinkan.

sementara C ++ 03 tidak memiliki fitur constexpr , kami tidak perlu menggunakan kata kunci constexpr secara eksplisit karena pustaka standar menyediakan banyak fungsi di C ++ 11 sebagai constexpr . Misalnya std :: numeric_limits :: min . Yang dapat menyebabkan perilaku berbeda, misalnya:

#include <limits>

int main()
{
    int x[std::numeric_limits<unsigned int>::min()+2] ;
}

Menggunakan clangdalam C ++ 03 ini akan menyebabkan xarray panjang variabel, yang merupakan ekstensi dan akan menghasilkan peringatan berikut:

warning: variable length arrays are a C99 feature [-Wvla-extension]
    int x[std::numeric_limits<unsigned int>::min()+2] ;
         ^

sedangkan di C ++ 11 std::numeric_limits<unsigned int>::min()+2adalah ekspresi konstanta waktu kompilasi dan tidak memerlukan ekstensi VLA.

6. Dalam C ++ 11 spesifikasi pengecualian noexcept secara implisit dihasilkan untuk destruktor Anda

Karena dalam C ++ 11 destruktor yang ditentukan pengguna memiliki noexcept(true)spesifikasi implisit seperti yang dijelaskan dalam destruktor noexcept itu berarti bahwa program berikut:

#include <iostream>
#include <stdexcept>

struct S
{
  ~S() { throw std::runtime_error(""); } // bad, but acceptable
};

int main()
{
  try { S s; }
  catch (...) {
    std::cerr << "exception occurred";
  } 
 std::cout << "success";
}

Di C ++ 11 akan memanggil std::terminatetetapi akan berhasil dijalankan di C ++ 03.

7. Dalam C ++ 03, argumen templat tidak dapat memiliki tautan internal

Ini tercakup dengan baik di Mengapa std :: sort tidak menerima kelas Bandingkan dideklarasikan dalam suatu fungsi . Jadi kode berikut seharusnya tidak berfungsi di C ++ 03:

#include <iostream>
#include <vector>
#include <algorithm>

class Comparators
{
public:
    bool operator()(int first, int second)
    {
        return first < second;
    }
};

int main()
{
    class ComparatorsInner : public Comparators{};

    std::vector<int> compares ;
    compares.push_back(20) ;
    compares.push_back(10) ;
    compares.push_back(30) ;

    ComparatorsInner comparatorInner;
    std::sort(compares.begin(), compares.end(), comparatorInner);

    std::vector<int>::iterator it;
    for(it = compares.begin(); it != compares.end(); ++it)
    {
        std::cout << (*it) << std::endl;
    }
}

tetapi saat clangini memungkinkan kode ini dalam mode C ++ 03 dengan peringatan kecuali Anda menggunakan -pedantic-errorsflag, yang agak menjijikkan, lihat langsung .

8. >> tidak lagi salah bentuk saat menutup beberapa templat

Menggunakan >>untuk menutup beberapa templat tidak lagi salah bentuk tetapi dapat menyebabkan kode dengan hasil berbeda di C ++ 03 dan C + 11. Contoh di bawah ini diambil dari kurung siku kanan dan kompatibilitas ke belakang :

#include <iostream>
template<int I> struct X {
  static int const c = 2;
};
template<> struct X<0> {
  typedef int c;
};
template<typename T> struct Y {
  static int const c = 3;
};
static int const c = 4;
int main() {
  std::cout << (Y<X<1> >::c >::c>::c) << '\n';
  std::cout << (Y<X< 1>>::c >::c>::c) << '\n';
}

dan hasilnya dalam C ++ 03 adalah:

0
3

dan di C ++ 11:

0
0

9. C ++ 11 mengubah beberapa konstruktor std :: vector

Kode yang sedikit dimodifikasi dari jawaban ini menunjukkan bahwa menggunakan konstruktor berikut dari std :: vector :

std::vector<T> test(1);

menghasilkan hasil yang berbeda di C ++ 03 dan C ++ 11:

#include <iostream>
#include <vector>

struct T
{
    bool flag;
    T() : flag(false) {}
    T(const T&) : flag(true) {}
};


int main()
{
    std::vector<T> test(1);
    bool is_cpp11 = !test[0].flag;

    std::cout << is_cpp11 << std::endl ;
}

10. Persempit konversi dalam inisialisasi agregat

Dalam C ++ 11 konversi penyempitan dalam inisialisasi agregat tidak terbentuk dan sepertinya gccmemungkinkan ini di C ++ 11 dan C ++ 03 meskipun itu memberikan peringatan secara default di C ++ 11:

int x[] = { 2.0 };

Ini dicakup dalam draft C ++ 11 bagian standar 8.5.4 Daftar-inisialisasi paragraf 3 :

Daftar-inisialisasi objek atau referensi tipe T didefinisikan sebagai berikut:

dan mengandung peluru berikut ( penekanan milikku ):

Kalau tidak, jika T adalah tipe kelas, konstruktor dianggap. Konstruktor yang berlaku disebutkan dan yang terbaik dipilih melalui resolusi kelebihan beban (13.3, 13.3.1.7). Jika konversi penyempitan (lihat di bawah) diperlukan untuk mengonversi salah satu argumen, program ini salah bentuk

Ini dan banyak lagi contoh dibahas dalam rancangan C ++ bagian standar annex C.2 C ++ dan ISO C ++ 2003 . Ini juga termasuk:

  • Jenis baru string literal [...] Secara khusus, makro bernama R, u8, u8R, u, uR, U, UR, atau LR tidak akan diperluas ketika berdekatan dengan string literal tetapi akan ditafsirkan sebagai bagian dari string literal . Sebagai contoh

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
  • Dukungan string literal yang ditentukan pengguna [...] Sebelumnya, # 1 akan terdiri dari dua token pemroses yang terpisah dan makro _x akan diperluas. Dalam Standar Internasional ini, # 1 terdiri dari satu token pemrosesan awal, sehingga makro tidak diperluas.

    #define _x "there"
    "hello"_x // #1
  • Tentukan pembulatan untuk hasil bilangan bulat / dan% [...] kode 2003 yang menggunakan pembulatan bilangan bulat bulat hasilnya ke 0 atau ke arah infinity negatif, sedangkan Standar Internasional ini selalu membulatkan hasilnya ke 0.

  • Kompleksitas ukuran () fungsi anggota sekarang konstan [...] Beberapa implementasi wadah yang sesuai dengan C ++ 2003 mungkin tidak sesuai dengan persyaratan ukuran yang ditentukan () dalam Standar Internasional ini. Menyesuaikan wadah seperti std :: list dengan persyaratan yang lebih ketat mungkin memerlukan perubahan yang tidak kompatibel.

  • Ubah kelas dasar std :: ios_base :: failure [...] std :: ios_base :: failure tidak lagi diturunkan langsung dari std :: exception, tetapi sekarang berasal dari std :: system_error, yang pada gilirannya berasal dari std :: runtime_error. Kode C ++ 2003 yang valid yang mengasumsikan bahwa std :: ios_base :: failure diturunkan langsung dari std :: exception dapat dieksekusi secara berbeda dalam Standar Internasional ini.

Shafik Yaghmour
sumber
Jadi sebagian besar contoh mempersempit fakta bahwa perilaku yang sebelumnya tidak terdefinisi sekarang didefinisikan dengan baik?
MatthiasB
@ MatthiasB 2, 3 dan 4 adalah tentang ini sehingga pada saat ini mereka bukan lagi mayoritas contoh. Saya ragu saya akan menemukan lebih banyak contoh perilaku yang tidak terdefinisi sehingga ketika saya menambahkan lebih banyak maka mereka akan menjadi kumpulan yang lebih kecil.
Shafik Yaghmour
Nah, perilaku # 1 tidak ditentukan, jadi saya akan menghitungnya sebagai perilaku yang tidak terdefinisi (setidaknya Anda tidak bisa mengharapkan untuk mendapatkan hasil spesifik dengan c ++ 03, sekarang dengan c ++ 11 Anda bisa), # 5 menggunakan non- ekstensi standar c ++. Tapi saya kira Anda benar. Semakin Anda mencarinya, semakin banyak contoh yang akan Anda temukan yang didefinisikan dalam kedua standar tetapi menghasilkan hasil yang berbeda.
MatthiasB
@ MatthiasB ya, perilaku yang tidak ditentukan dan tidak terdefinisi memiliki hasil yang tidak diinginkan. Adapun ekstensi mempertimbangkan Linux tergantung pada sejumlah ekstensi gcc kita harus menganggap di dunia nyata itu penting. Saya tidak berharap untuk menemukan begitu banyak contoh ketika saya pertama kali menjawab pertanyaan ini.
Shafik Yaghmour
35

Salah satu perubahan yang berpotensi tidak kompatibel ke belakang yang berbahaya adalah pada konstruktor kontainer berurutan seperti std::vector, khususnya dalam kelebihan menentukan ukuran awal. Di mana di C ++ 03, mereka menyalin elemen bawaan-dibangun, di C ++ 11 mereka default-membangun masing-masing.

Pertimbangkan contoh ini (menggunakan boost::shared_ptrsehingga valid C ++ 03):

#include <deque>
#include <iostream>

#include "boost/shared_ptr.hpp"


struct Widget
{
  boost::shared_ptr<int> p;

  Widget() : p(new int(42)) {}
};


int main()
{
  std::deque<Widget> d(10);
  for (size_t i = 0; i < d.size(); ++i)
    std::cout << "d[" << i << "] : " << d[i].p.use_count() << '\n';
}

C ++ 03 Contoh langsung

C ++ 11 Contoh langsung

Alasannya adalah bahwa C ++ 03 menetapkan satu kelebihan untuk "tentukan ukuran dan elemen prototipe" dan "tentukan ukuran saja," seperti ini (argumen pengalokasi dihilangkan karena singkatnya):

container(size_type size, const value_type &prototype = value_type());

Ini akan selalu menyalin prototypeke sizekali wadah . Ketika dipanggil hanya dengan satu argumen, karenanya akan membuat sizesalinan dari elemen yang dibangun secara default.

Di C ++ 11, tanda tangan konstruktor ini dihapus dan diganti dengan dua kelebihan ini:

container(size_type size);

container(size_type size, const value_type &prototype);

Yang kedua berfungsi seperti sebelumnya, membuat sizesalinan prototypeelemen. Namun, yang pertama (yang sekarang menangani panggilan dengan hanya argumen ukuran yang ditentukan) default-membangun setiap elemen secara individual.

Dugaan saya untuk alasan perubahan ini adalah bahwa kelebihan C ++ 03 tidak akan dapat digunakan dengan tipe elemen yang hanya bergerak. Tapi itu perubahan yang luar biasa, dan orang jarang mendokumentasikannya.

Angew tidak lagi bangga dengan SO
sumber
3
Meskipun ini jelas merupakan perubahan besar, saya lebih suka perilaku C ++ 11. Saya berharap ini menghasilkan dequesepuluh widget terpisah, bukan sepuluh widget berbagi sumber daya yang sama.
Agentlien
19

Hasil pembacaan gagal dari std::istreamtelah berubah. CppReference merangkumnya dengan baik:

Jika ekstraksi gagal (misalnya jika huruf dimasukkan di mana angka diharapkan), valuedibiarkan tidak dimodifikasi dan failbitdiatur. (sampai C ++ 11)

Jika ekstraksi gagal, nol ditulis ke valuedan failbitdiatur. Jika ekstraksi menghasilkan nilai yang terlalu besar atau terlalu kecil untuk ditampung value, std::numeric_limits<T>::max()atau std::numeric_limits<T>::min()ditulis dan failbitflag ditetapkan. (sejak C ++ 11)

Ini terutama masalah jika Anda terbiasa dengan semantik baru dan kemudian harus menulis menggunakan C ++ 03. Berikut ini bukan praktik yang sangat baik tetapi didefinisikan dengan baik dalam C ++ 11:

int x, y;
std::cin >> x >> y;
std::cout << x + y;

Namun, dalam C ++ 03, kode di atas menggunakan variabel yang tidak diinisialisasi dan dengan demikian memiliki perilaku yang tidak terdefinisi.

Anton Golov
sumber
4
Anda dapat menambahkan, bahwa dalam C ++ 03 seseorang dapat menggunakan perilaku terstandarisasi ini untuk memberikan nilai default, seperti pada int x = 1, y = 1; cin >> x >> y; cout << x*y;. Dengan C ++ 03, ini akan menghasilkan dengan benar xketika tidak ada yang ybisa dibaca.
cmaster - mengembalikan monica
15

Utas ini Perbedaan apa, jika ada, antara C ++ 03 dan C ++ 0x yang dapat dideteksi saat run-time memiliki contoh (disalin dari utas itu) untuk menentukan perbedaan bahasa, misalnya dengan mengeksploitasi referensi C ++ 11 yang runtuh:

template <class T> bool f(T&) {return true; } 
template <class T> bool f(...){return false;} 

bool isCpp11() 
{
    int v = 1;
    return f<int&>(v); 
}

dan c ++ 11 memungkinkan tipe lokal sebagai parameter templat:

template <class T> bool cpp11(T)  {return true;} //T cannot be a local type in C++03
                   bool cpp11(...){return false;}

bool isCpp0x() 
{
   struct local {} var; //variable with local type
   return cpp11(var);
}
uwedolinsky
sumber
7

Ini contoh lain:

#include <iostream>

template<class T>
struct has {
  typedef char yes;
  typedef yes (&no)[2];    
  template<int> struct foo;    
  template<class U> static yes test(foo<U::bar>*);      
  template<class U> static no  test(...);    
  static bool const value = sizeof(test<T>(0)) == sizeof(yes);
};

enum foo { bar };

int main()
{
    std::cout << (has<foo>::value ? "yes" : "no") << std::endl;
}

Cetakan:

Using c++03: no
Using c++11: yes

Lihat hasilnya di Coliru

StackedCrooked
sumber