web-dev-qa-db-fra.com

Le code C ++ peut-il être valide à la fois en C ++ 03 et en C ++ 11 mais faire des choses différentes?

Est-il possible que le code C++ soit conforme à la fois à la norme C++ et à la norme C++ 11 , mais fait des choses différentes selon la norme sous laquelle il est en train d'être compilé?

295
Erik Sjölund

La réponse est un oui catégorique. Du côté positif, il y a:

  • Le code des objets précédemment copiés implicitement les déplacera implicitement dans la mesure du possible.

Du côté négatif, plusieurs exemples sont énumérés dans l'annexe C de la norme. Même s'il y a beaucoup plus de négatifs que de positifs, chacun d'entre eux est beaucoup moins susceptible de se produire.

Littéraux de chaîne

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

et

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

Tapez les conversions de 0

En C++ 11, seuls les littéraux sont des constantes de pointeur null entières:

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

Résultats arrondis après la division entière et modulo

En C++ 03, le compilateur était autorisé à arrondir vers l'infini négatif ou vers 0. En C++ 11, il est obligatoire d’arrondir vers 0

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

Espaces entre les accolades de fermeture du modèle imbriqué >> vs>>

Dans une spécialisation ou une instanciation, le >> Pourrait plutôt être interprété comme un décalage à droite en C++ 03. Cela risque davantage de casser le code existant: (from http://gustedt.wordpress.com/2013/12/15/a-disimprovement-observé-de-la-outside-right-angle -supports/ )

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'opérateur new peut maintenant générer d'autres exceptions que std::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
}

Les destructeurs déclarés par l'utilisateur ont une spécification d'exception implicite exemple tiré de Quelles modifications radicales sont introduites dans C++ 11?

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

size() des conteneurs est maintenant requis pour s'exécuter en O (1)

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

std::ios_base::failure Ne dérive plus directement de std::exception

Alors que la classe de base directe est nouvelle, std::runtime_error Ne l’est pas. Ainsi:

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

Je vous indique cet article et le suivi , qui a un bel exemple de la façon dont >> peut changer le sens de C++ 03 à C++ 11 tout en compilant les deux.

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 partie clé est la ligne dans main, qui est une expression.

En C++ 03:

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

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

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

En 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

Félicitations, deux résultats différents pour la même expression. Certes, le C++ 03 est venu avec un avertissement Clang lorsque je l'ai testé.

55
chris

Oui, plusieurs modifications entraînent un comportement différent du même code entre C++ 03 et C++ 11. Les différences de règles de séquençage apportent des modifications intéressantes, notamment que certains comportements auparavant non définis deviennent bien définis.

1. plusieurs mutations de la même variable dans une liste d'initialiseurs

Un cas de coin très intéressant serait plusieurs mutations de la même variable dans une liste d'initialisation, par exemple:

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

    return 0 ;
}

En C++ 03 et en C++ 11, cela est bien défini mais l'ordre d'évaluation en C++ 03 n'est pas spécifié mais en C++ 11, ils sont évalués dans l'ordre dans lequel ils apparaissent . Donc, si nous compilons en utilisant clang en mode C++ 03, il fournit l’avertissement suivant (le voir en direct):

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

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

                           ^        ~~

mais ne fournit pas d'avertissement en C++ 11 (le voir en direct).

2. Les nouvelles règles de séquençage font i = ++ i + 1; bien défini en C++ 11

Les nouvelles règles de séquencement adoptées après C++ 03 signifient que:

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

n’est plus un comportement indéfini en C++ 11, il est couvert dans rapport de défaut 637. Règles de séquençage et exemple en désaccord

3. Les nouvelles règles de séquençage font également ++++ i; bien défini en C++ 11

Les nouvelles règles de séquencement adoptées après C++ 03 signifient que:

int i = 0 ;
++++i ;

n'est plus un comportement indéfini dans C++ 11.

4. Décalage à gauche légèrement plus sensible signé

Les brouillons ultérieurs de C++ 11 incluent N3485 Que je lie ci-dessous corrige le comportement indéfini consistant à décaler un bit de 1 dans ou au-delà du bit de signe . Ceci est également couvert dans rapport de défaut 1457 . Howard Hinnant a commenté l’importance de ce changement dans le fil de discussion sur Est-ce que le décalage à gauche (<<) est un comportement indéfini avec un entier négatif en C++ 11? .

5. Les fonctions constexpr peuvent être traitées comme des expressions constantes de temps de compilation en C++ 11

C++ 11 introduit constexpr fonctions qui:

Le spécificateur constexpr déclare qu'il est possible d'évaluer la valeur de la fonction ou de la variable au moment de la compilation. Ces variables et fonctions peuvent ensuite être utilisées lorsque seules les expressions à constante de temps de compilation sont autorisées.

bien que C++ 03 ne possède pas la fonctionnalité constexpr , nous n'avons pas à utiliser explicitement le mot clé constexpr puisque la bibliothèque standard fournit de nombreuses fonctions dans C++ 11 sous la forme constexpr . Par exemple std :: numeric_limits :: min . Ce qui peut conduire à un comportement différent, par exemple:

#include <limits>

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

Si vous utilisez clang en C++ 03, cela fera de x un tableau de longueur variable, qui est ne extension , et générera l'avertissement suivant:

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

tandis que dans C++ 11, std::numeric_limits<unsigned int>::min()+2 est une expression constante de compilation qui ne nécessite pas l'extension VLA.

6. En C++ 11, aucune spécification d'exception n'est générée implicitement pour vos destructeurs.

Comme dans C++ 11, le destructeur défini par l'utilisateur a la spécification implicite noexcept(true), comme expliqué dans noexcept destructors , cela signifie que le programme suivant:

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

En C++ 11 appellera std::terminate Mais s'exécutera avec succès en C++ 03.

7. En C++ 03, les arguments de modèle ne pouvaient pas avoir de liaison interne

Ceci est couvert joliment dans Pourquoi std :: sort n'accepte pas les classes de comparaison déclarées dans une fonction . Le code suivant ne devrait donc pas fonctionner en 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;
    }
}

mais actuellement clang autorise ce code en mode C++ 03 avec un avertissement sauf si vous utilisez l'indicateur -pedantic-errors, qui est un peu icky, le voir en direct .

8. >> n'est plus mal formé lors de la fermeture de plusieurs modèles

L'utilisation de >> Pour fermer plusieurs modèles n'est plus mal formée, mais peut conduire à un code avec des résultats différents en C++ 03 et C + 11. L'exemple ci-dessous est tiré de crochets droits et compatibilité ascendante :

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

et le résultat en C++ 03 est:

0
3

et en C++ 11:

0
0

9. C++ 11 modifie certains des constructeurs std :: vector

Le code légèrement modifié de cette réponse montre que l'utilisation du constructeur suivant de std :: vector :

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

produit différents résultats en C++ 03 et 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. Réduire les conversions dans les initialiseurs globaux

En C++ 11, une conversion restrictive dans les initialiseurs agrégés est mal formée et il semble que gcc le permette à la fois en C++ 11 et en C++ 03, bien qu'elle fournisse un avertissement par défaut en C++ 11 :

int x[] = { 2.0 };

Ceci est couvert dans le projet de section standard C++ 11 8.5.4 Initialisation de liste paragraphe 3 :

L'initialisation de liste d'un objet ou d'une référence de type T est définie comme suit:

et contient la puce suivante ( emphase mine ):

Sinon, si T est un type de classe, les constructeurs sont pris en compte. Les constructeurs applicables sont énumérés et le meilleur est choisi par résolution de surcharge (13.3, 13.3.1.7). Si une conversion restrictive (voir ci-dessous) est nécessaire pour convertir l'un des arguments, le programme est mal formé

Cette instance et bien d’autres sont traitées dans la section projet de norme C++annex C.2 C++ et ISO C++ 2003 . Il comprend également:

  • Nouveaux types de littéraux de chaîne [...] Plus précisément, les macros nommées R, u8, u8R, u, uR, U, UR ou LR ne seront pas développées lorsqu'elles sont adjacentes à un littéral de chaîne, mais seront interprétées comme faisant partie du littéral de chaîne. . Par exemple

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
    
  • Prise en charge de chaînes littérales définies par l'utilisateur [...] Auparavant, # 1 aurait consisté en deux jetons de prétraitement distincts et la macro _x aurait été développée. Dans la présente Norme internationale, le n ° 1 est constitué d'un seul jeton de prétraitement, de sorte que la macro n'est pas développée.

    #define _x "there"
    "hello"_x // #1
    
  • Spécifier l'arrondi pour les résultats de code entier/et% [...] 2003 utilisant une division entière arrondit le résultat vers 0 ou vers l'infini négatif, alors que la présente Norme internationale arrondit toujours le résultat vers 0.

  • La complexité des fonctions membres de size () est désormais constante [...] Certaines implémentations de conteneur conformes à C++ 2003 peuvent ne pas être conformes aux exigences de size () spécifiées dans la présente Norme internationale. Ajuster les conteneurs tels que std :: list aux exigences plus strictes peut nécessiter des modifications incompatibles.

  • Changer la classe de base de std :: ios_base :: failure [...] std :: ios_base :: failure n'est plus dérivé directement de std :: exception, mais est maintenant dérivé de std :: system_error, qui est lui-même dérivé de std :: runtime_error. Le code C++ 2003 valide qui suppose que std :: ios_base :: failure est directement dérivé de std :: exception peut s'exécuter différemment dans la présente Norme internationale.

37
Shafik Yaghmour

Une modification potentiellement dangereuse incompatible avec les versions antérieures concerne les constructeurs de conteneurs de séquence tels que std::vector, en particulier dans la surcharge spécifiant la taille initiale. Où, en C++ 03, ils ont copié un élément construit par défaut, en C++ 11, ils les ont construits par défaut.

Considérons cet exemple (en utilisant boost::shared_ptr pour qu'il soit valide 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';
}

Exemple C++ 03 Live

Exemple C++ 11 Live

La raison en est que C++ 03 a spécifié une surcharge pour "spécifier la taille et l'élément prototype" et "spécifier uniquement la taille", comme ceci (arguments d'allocation omis par souci de brièveté):

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

Ceci copie toujours prototype dans le conteneur size fois. Appelé avec un seul argument, il créera donc size copies d'un élément construit par défaut.

En C++ 11, cette signature du constructeur a été supprimée et remplacée par les deux surcharges suivantes:

container(size_type size);

container(size_type size, const value_type &prototype);

Le second fonctionne comme précédemment, en créant size copies de l’élément prototype. Cependant, le premier (qui gère désormais les appels avec uniquement l'argument de taille spécifié) par défaut, construit chaque élément individuellement.

Je suppose que la raison de ce changement est que la surcharge C++ 03 ne serait pas utilisable avec un type d'élément de déplacement uniquement. Mais c’est néanmoins un changement radical et rarement documenté.

35
Angew

Le résultat d'une lecture ayant échoué à partir d'un std::istream A changé. CppReference le résume bien:

Si l'extraction échoue (par exemple, si une lettre a été entrée où un chiffre est attendu), value n'est pas modifié et failbit est défini. (jusqu'à C++ 11)

Si l'extraction échoue, zéro est écrit dans value et failbit est défini. Si l'extraction donne une valeur trop grande ou trop petite pour tenir dans value, std::numeric_limits<T>::max() ou std::numeric_limits<T>::min() est écrit et le drapeau failbit est défini. (depuis C++ 11)

C'est principalement un problème si vous êtes habitué à la nouvelle sémantique et que vous devez ensuite écrire en C++ 03. Ce qui suit n'est pas une bonne pratique mais est bien défini en C++ 11:

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

Cependant, en C++ 03, le code ci-dessus utilise une variable non initialisée et a donc un comportement indéfini.

19
Anton Golov

Ce fil Les différences éventuelles entre C++ 03 et C++ 0x peuvent être détectées à l'exécution contient des exemples (copiés à partir de ce fil) permettant de déterminer les différences de langage, par exemple en exploitant C++ 11 réduction de référence:

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

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

et c ++ 11 autorisant les types locaux comme paramètres de modèle:

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

Voici un autre exemple:

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

Impressions:

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

Voir le résultat sur Colir

7
StackedCrooked