Il codice C ++ può essere valido in C ++ 03 e C ++ 11 ma può fare cose diverse?


299

È possibile che il codice C ++ sia conforme allo standard C ++ 03 e allo standard C ++ 11 , ma che facciano cose diverse a seconda di quale standard viene compilato?


26
Sono abbastanza sicuro che autopotrebbe tradursi in una situazione come questa
OMGtechy,

8
Sì. Un esempio è >>quando utilizzato in un modello. È possibile trovare una situazione in cui è possibile compilare per entrambi gli standard. Un altro per cui sono sicuro che sarebbe facile trovare le modifiche è l'inizializzazione.
chris,

5
Ecco un bell'articolo sulla >> situazione: gustedt.wordpress.com/2013/12/15/…
chris,

6
@OMGtechy: non credo che auto possa causare questo. Con il vecchio significato, una autodichiarazione richiede un nome di tipo; con il nuovo significato, un nome di tipo non è consentito.
Keith Thompson,

2
Come è a tempo indeterminato? Lei stesso ha sottolineato con un'altra domanda che la risposta a questa domanda è "sì, ecco un esempio di come". C'è una risposta molto precisa alla domanda, come tu stesso hai sottolineato.
jalf

Risposte:


284

La risposta è assolutamente si. Tra i lati positivi c'è:

  • Il codice che precedentemente ha copiato implicitamente gli oggetti ora li sposta implicitamente quando possibile.

Sul lato negativo, diversi esempi sono elencati nell'appendice C dello standard. Anche se ci sono molti più negativi che positivi, ognuno di essi ha molte meno probabilità di verificarsi.

Letterali a corda

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

e

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

Digita conversioni di 0

In C ++ 11, solo i letterali sono costanti di puntatore null intero:

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

Risultati arrotondati dopo divisione intera e modulo

In C ++ 03 al compilatore è stato permesso di arrotondare verso 0 o verso l'infinito negativo. In C ++ 11 è obbligatorio arrotondare verso 0

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

Spazi bianchi tra parentesi graffe di modello nidificate >> vs>>

All'interno di una specializzazione o istanziazione, >>invece, potrebbe essere interpretato come uno spostamento a destra in C ++ 03. È più probabile che ciò rompa il codice esistente: (da 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);
}

L'operatore newpuò ora generare altre eccezioni rispetto astd::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
}

I distruttori dichiarati dall'utente hanno un esempio implicito di specifica dell'eccezione da Quali modifiche di rottura sono state introdotte in C ++ 11?

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

size() dei contenitori è ora necessario per funzionare in O (1)

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

std::ios_base::failurenon deriva direttamente da std::exceptionpiù

Mentre la classe base diretta è nuova, std::runtime_errornon lo è. Così:

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";
}

11
Bello, +1. Un altro è che un utente dichiarato distruttore ora è implicitamente, noexecpt(true)quindi throwin un distruttore ora chiamerà std::terminate. Ma spero che chiunque abbia scritto questo codice sia contento di questo!
typ1232

4
Ma std :: system_error stesso è (indirettamente) derivato da std :: exception, quindi catch (std::exception &)continua a catturare std::ios_base::failure.
user2665887

@ user2665887 hai ragione. può ancora influenzare il comportamento di un programma, ma non riesco a pensare a un esempio minimo in questo momento.
esempio

4
Sono super confuso, poiché quello che dici operator newè accurato (ora può essere lanciato std::bad_array_new_length), ma il tuo esempio non lo mostra affatto. Il codice che mostri è lo stesso in C ++ 03 e C ++ 11 AFAIK.
Mooing Duck,

2
Il rovescio della medaglia della lista :: la dimensione è O (1) è che la giuntura è ora O (n)
Tony Delroy,

56

Ti faccio riferimento a questo articolo e al follow-up , che ha un bell'esempio di come >>può cambiare significato da C ++ 03 a C ++ 11 mentre si compila ancora in entrambi.

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  
}

La parte chiave è la linea in main, che è un'espressione.

In C ++ 03:

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

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

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

In 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

Congratulazioni, due risultati diversi per la stessa espressione. Certo, il C ++ 03 ha prodotto un modulo di avvertimento Clang quando l'ho provato.


è strano che non richiede typenameper ::twoin C ++ 03 versione
Zahir

3
Bello, farlo bollire per valutare trueo falseper i diversi standard. Forse potremmo usarlo come test di funzionalità </joke>
cmaster - reintegrare monica il

@zahir, non è un tipo, solo un valore.
chris,

bene, le opportune opzioni cmdline mettono in guardia su questo ( warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]), ma è ancora un buon esempio di come l' ::operatore ambiguo cambi significato (o riferendosi all'ambito globale o dereferenziando quello che sta direttamente davanti ad esso)
esempio

@esempio, Abbastanza sorprendentemente, GCC dà quell'avvertimento, ma Clang no.
chris,

39

Sì, ci sono un numero di modifiche che causeranno lo stesso codice a comportare comportamenti diversi tra C ++ 03 e C ++ 11. Le differenze delle regole di sequenziamento rendono alcune modifiche interessanti tra cui alcuni comportamenti precedentemente non definiti che diventano ben definiti.

1. mutazioni multiple della stessa variabile all'interno di un elenco di inizializzatori

Un caso d'angolo molto interessante sarebbe più mutazioni della stessa variabile all'interno di un elenco di inizializzatori, ad esempio:

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

    return 0 ;
}

In C ++ 03 e C ++ 11 questo è ben definito, ma l' ordine di valutazione in C ++ 03 non è specificato ma in C ++ 11 sono valutati nell'ordine in cui appaiono . Quindi, se compiliamo utilizzando clangin modalità C ++ 03, fornisce il seguente avviso ( vederlo dal vivo ):

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

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

                           ^        ~~

ma non fornisce un avviso in C ++ 11 ( vederlo dal vivo ).

2. Le nuove regole di sequenziamento rendono i = ++ i + 1; ben definito in C ++ 11

Le nuove regole di sequenziamento adottate dopo C ++ 03 significano che:

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

non è più un comportamento indefinito in C ++ 11, questo è trattato nel rapporto sui difetti 637. Le regole di sequenza e l'esempio non sono d'accordo

3. Nuove regole di sequenziamento rendono anche ++++ i; ben definito in C ++ 11

Le nuove regole di sequenziamento adottate dopo C ++ 03 significano che:

int i = 0 ;
++++i ;

non è più un comportamento indefinito in C ++ 11.

4. Spostamenti a sinistra firmati leggermente più sensibili

Le bozze successive di C ++ 11 includono N3485quali collegamenti sotto risolto il comportamento indefinito di spostamento di 1 bit dentro o oltre il bit di segno . Questo è anche coperto dal rapporto sui difetti 1457 . Howard Hinnant ha commentato il significato di questo cambiamento nel thread su Spostamento a sinistra (<<) un comportamento intero negativo non definito in C ++ 11? .

5. Le funzioni constexpr possono essere trattate come espressioni costanti di tempo di compilazione in C ++ 11

C ++ 11 ha introdotto le funzioni constexpr che:

Lo specificatore constexpr dichiara che è possibile valutare il valore della funzione o della variabile in fase di compilazione. Tali variabili e funzioni possono quindi essere utilizzate laddove sono consentite solo espressioni di costanti di tempo di compilazione.

mentre C ++ 03 non ha la funzione constexpr , non è necessario utilizzare esplicitamente la parola chiave constexpr poiché la libreria standard fornisce molte funzioni in C ++ 11 come constexpr . Ad esempio std :: numeric_limits :: min . Che può portare a comportamenti diversi, ad esempio:

#include <limits>

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

Usando clangin C ++ 03 questo causerà xun array di lunghezza variabile, che è un'estensione e genererà il seguente avviso:

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

mentre in C ++ 11 std::numeric_limits<unsigned int>::min()+2è un'espressione costante di compilazione e non richiede l'estensione VLA.

6. In C ++ 11 le eccezioni senza eccezioni sono generate implicitamente per i distruttori

Poiché in C ++ 11 il distruttore definito dall'utente ha una noexcept(true)specifica implicita come spiegato nei distruttori noexcept , significa che il seguente programma:

#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";
}

In C ++ 11 chiamerà std::terminatema verrà eseguito correttamente in C ++ 03.

7. In C ++ 03, gli argomenti del modello non potevano avere un collegamento interno

Questo è ben spiegato in Perché std :: sort non accetta le classi di confronto dichiarate all'interno di una funzione . Quindi il codice seguente non dovrebbe funzionare in 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;
    }
}

ma attualmente clangconsente questo codice in modalità C ++ 03 con un avviso a meno che non si usi -pedantic-errorsflag, che è un po 'icky, vederlo dal vivo .

8. >> non è più mal formato quando si chiudono più modelli

L'uso >>per chiudere più modelli non è più mal formato ma può portare a codice con risultati diversi in C ++ 03 e C + 11. L'esempio seguente è tratto dalle parentesi ad angolo retto e dalla compatibilità con le versioni precedenti :

#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';
}

e il risultato in C ++ 03 è:

0
3

e in C ++ 11:

0
0

9. C ++ 11 modifica alcuni dei costruttori std :: vector

Il codice leggermente modificato da questa risposta mostra che usando il seguente costruttore da std :: vector :

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

produce risultati diversi in C ++ 03 e 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. Restringere le conversioni negli inizializzatori aggregati

In C ++ 11 una conversione restrittiva negli inizializzatori aggregati è mal formata e sembra gccconsentire ciò sia in C ++ 11 che in C ++ 03 sebbene fornisca un avviso di default in C ++ 11:

int x[] = { 2.0 };

Questo è trattato nella bozza della sezione standard C ++ 11 8.5.4 Inizializzazione elenco paragrafo 3 :

L'inizializzazione dell'elenco di un oggetto o un riferimento di tipo T è definita come segue:

e contiene il seguente punto elenco (il mio accento è mio ):

Altrimenti, se T è un tipo di classe, vengono considerati i costruttori. Vengono elencati i costruttori applicabili e il migliore viene scelto tramite risoluzione di sovraccarico (13.3, 13.3.1.7). Se è richiesta una conversione restrittiva (vedi sotto) per convertire uno qualsiasi degli argomenti, il programma è mal formato

Questo e molti altri sono coperti esempio nel progetto C ++ standard di sezione annex C.2 C ++ e C ++ ISO 2003 . Include anche:

  • Nuovi tipi di valori letterali di stringa [...] In particolare, le macro denominate R, u8, u8R, u, uR, U, UR o LR non verranno espanse quando sono adiacenti a un valore letterale di stringa ma verranno interpretate come parte del valore letterale di stringa . Per esempio

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
  • Supporto stringa letterale definito dall'utente [...] In precedenza, il numero 1 era costituito da due token di preelaborazione separati e la macro _x sarebbe stata espansa. In questo standard internazionale, il numero 1 consiste in un singolo token di preelaborazione, quindi la macro non viene espansa.

    #define _x "there"
    "hello"_x // #1
  • Specificare l'arrotondamento per i risultati del codice intero / e% [...] 2003 che utilizza la divisione di numeri interi arrotonda il risultato verso 0 o verso l'infinito negativo, mentre questo standard internazionale arrotonda sempre il risultato verso 0.

  • La complessità delle funzioni membro size () ora è costante [...] Alcune implementazioni di container conformi a C ++ 2003 potrebbero non essere conformi ai requisiti size () specificati in questo standard internazionale. Adattare i contenitori come std :: list ai requisiti più severi può richiedere modifiche incompatibili.

  • Cambia la classe base di std :: ios_base :: failure [...] std :: ios_base :: failure non deriva più direttamente da std :: exception, ma ora deriva da std :: system_error, che a sua volta deriva da std :: runtime_error. Il codice C ++ 2003 valido che presuppone che std :: ios_base :: failure sia derivato direttamente da std :: exception può essere eseguito in modo diverso in questo standard internazionale.


Quindi la maggior parte degli esempi si restringe al fatto che il comportamento precedentemente indefinito è ora ben definito?
MatthiasB,

@MatthiasB 2, 3 e 4 parlano di questo, quindi a questo punto non sono più la maggioranza degli esempi. Dubito che troverò molti altri esempi di comportamento indefinito, quindi quando ne aggiungo altri diventeranno un set più piccolo.
Shafik Yaghmour,

Bene, il comportamento # 1 non è specificato, quindi lo considero un comportamento indefinito (almeno non puoi aspettarti di ottenere un risultato specifico con c ++ 03, ora con c ++ 11 puoi), # 5 usa un non- estensione standard di c ++. Ma suppongo tu abbia ragione. Più lo cerchi, più esempi troverai che sono definiti in entrambi gli standard ma producono risultati diversi.
MatthiasB,

@MatthiasB sì, sia il comportamento non specificato che quello indefinito hanno risultati indesiderati. Per quanto riguarda le estensioni che considerano Linux dipende da una serie di estensioni gcc che dovremmo assumere nel mondo reale che contano. Non mi aspettavo di trovare tanti esempi quando ho risposto per la prima volta a questa domanda.
Shafik Yaghmour,

35

Una modifica potenzialmente incompatibile all'indietro potenzialmente pericolosa è nei costruttori di contenitori di sequenze come std::vector, in particolare nel sovraccarico che specifica la dimensione iniziale. Dove in C ++ 03, hanno copiato un elemento costruito per impostazione predefinita, in C ++ 11 hanno costruito per default ciascuno di essi.

Considera questo esempio (usando in boost::shared_ptrmodo che sia valido 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 Esempio live

C ++ 11 Esempio live

Il motivo è che C ++ 03 ha specificato un sovraccarico sia per "specifica dimensione che per l'elemento prototipo" che per "specifica solo la dimensione", in questo modo (argomenti dell'allocatore omessi per brevità):

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

Questo verrà sempre copiato prototypenei sizetempi del contenitore . Se chiamato con un solo argomento, creerà quindi sizecopie di un elemento predefinito.

In C ++ 11, questa firma del costruttore è stata rimossa e sostituita con questi due sovraccarichi:

container(size_type size);

container(size_type size, const value_type &prototype);

Il secondo funziona come prima, creando sizecopie prototypedell'elemento. Tuttavia, il primo (che ora gestisce le chiamate con solo l'argomento size specificato) crea automaticamente ogni elemento singolarmente.

La mia ipotesi per il motivo di questa modifica è che il sovraccarico di C ++ 03 non sarebbe utilizzabile con un tipo di elemento solo spostamento. Ma è comunque un cambiamento radicale, e raramente ne è documentato uno.


3
Mentre questo è ovviamente un cambiamento decisivo, preferisco il comportamento C ++ 11. Mi aspetto che ciò comporti una dequetenuta di dieci widget separati, non di dieci widget che condividono la stessa risorsa.
Agentlien,

19

Il risultato di una lettura non riuscita da un std::istreamè cambiato. CppReference lo riassume bene:

Se l'estrazione non riesce (ad es. Se è stata inserita una lettera nel punto in cui è prevista una cifra), non valueviene modificata e failbitviene impostata. (fino a C ++ 11)

Se l'estrazione non riesce, viene scritto zero valuee failbitviene impostato. Se l'estrazione risulta in un valore troppo grande o troppo piccolo per adattarsi value, std::numeric_limits<T>::max()oppure std::numeric_limits<T>::min()è scritto e il failbitflag è impostato. (dal C ++ 11)

Questo è principalmente un problema se sei abituato alla nuova semantica e poi devi scrivere usando C ++ 03. Quella che segue non è particolarmente buona pratica ma ben definita in C ++ 11:

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

Tuttavia, in C ++ 03, il codice sopra riportato utilizza una variabile non inizializzata e pertanto ha un comportamento indefinito.


4
Si potrebbe aggiungere che in C ++ 03 si sarebbe potuto usare questo comportamento standardizzato per fornire un valore predefinito, come in int x = 1, y = 1; cin >> x >> y; cout << x*y;. Con C ++ 03, ciò avrebbe prodotto correttamente xquando non si ypoteva leggere.
cmaster - reintegrare monica il

15

Questo thread Quali differenze, se presenti, tra C ++ 03 e C ++ 0x possono essere rilevate in fase di esecuzione ha esempi (copiati da quel thread) per determinare le differenze di lingua, ad esempio sfruttando il collasso del riferimento C ++ 11:

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

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

e c ++ 11 che consente i tipi locali come parametri del modello:

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);
}

7

Ecco un altro esempio:

#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;
}

stampe:

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

Vedi il risultato su Coliru

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.