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

299

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

Erik Sjölund
la source
26
Je suis presque sûr que cela autopourrait entraîner une situation comme celle-ci
OMGtechy
8
Oui. Un exemple est >>lorsqu'il est utilisé dans un modèle. Vous pouvez trouver une situation où il peut compiler pour les deux normes. Un autre qui, j'en suis sûr, serait facile à trouver est celui de l'initialisation.
chris
5
Voici un bel article sur la >> situation: gustedt.wordpress.com/2013/12/15/…
chris
6
@OMGtechy: Je ne pense pas que celaauto puisse provoquer cela. Avec l'ancienne signification, une autodéclaration nécessite un nom de type; avec la nouvelle signification, un nom de type n'est pas autorisé.
Keith Thompson
2
Comment est-il ouvert? Vous avez vous-même souligné par une autre question que la réponse à cette question est "oui, voici un exemple de comment". Il y a une réponse très précise à la question, comme vous l'avez souligné vous-même.
jalf le

Réponses:

284

La réponse est définitivement affirmative. Du côté positif, il y a:

  • Le code qui copiait précédemment des objets implicitement les déplacera désormais implicitement lorsque cela sera possible.

Côté négatif, plusieurs exemples sont répertoriés dans l'annexe C de la norme. Même s'il y en a beaucoup plus de négatifs que de positifs, chacun d'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

Type de conversions de 0

En C ++ 11, seuls les littéraux sont des constantes de pointeur nul entier:

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 division entière et modulo

En C ++ 03, le compilateur était autorisé à arrondir vers 0 ou vers l'infini négatif. 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 fermantes du modèle imbriqué >> vs>>

À l'intérieur d'une spécialisation ou d'une instanciation, le >>pourrait plutôt être interprété comme un décalage à droite en C ++ 03. Cependant, cela est plus susceptible de casser le code existant: (à partir de 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'opérateur newpeut désormais lever d'autres exceptions questd::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 un exemple de spécification d'exception implicite tiré de Quelles modifications de rupture 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 désormais requis pour fonctionner 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 pas directement de std::exception plus

Bien que la classe de base directe soit nouvelle, elle std::runtime_errorne l'est pas. Donc:

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";
}
exemple
la source
11
Nice, +1. Un autre est qu'un utilisateur déclaré destructeur est maintenant implicitement noexecpt(true)donc throwdans un destructeur va maintenant appeler std::terminate. Mais j'espère que quiconque a écrit un tel code en sera heureux!
typ1232
4
Mais std :: system_error lui-même est (indirectement) dérivé de std :: exception, donc catch (std::exception &)il attrape toujours std::ios_base::failure.
user2665887
@ user2665887 vous avez raison. il peut encore influencer le comportement d'un programme, mais je ne peux pas penser à un exemple minimal en ce moment.
exemple
4
Je suis super confus, car ce que vous dites operator newest précis (il peut maintenant être lancé std::bad_array_new_length), mais votre exemple ne le montre pas du tout. Le code que vous montrez est le même en C ++ 03 et C ++ 11 AFAIK.
Mooing Duck
2
Le revers de la liste :: la taille étant O (1), c'est que l'épissure est maintenant O (n)
Tony Delroy
56

Je vous renvoie à cet article et à la suite , qui a un bel exemple de la façon de >>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  
}

L'élément clé est la ligne 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 a fourni un formulaire d'avertissement Clang lorsque je l'ai testé.

chris
la source
il est étrange qu'il ne nécessite pas typenamepour ::twoen C ++ 03 Version
zâhir
3
Bien joué, il se résume à évaluer selon trueou falsepour les différentes normes. Nous pourrions peut-être l'utiliser comme test de fonctionnalité </joke>
cmaster - reinstate monica
@zahir, ce n'est pas un type, juste une valeur.
chris
bien, les options de cmdline appropriées avertissent à ce sujet ( warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]), mais toujours un bel exemple de la façon dont l' ::opérateur ambigu change le sens (en se référant à la portée globale ou en déréférençant celui qui se trouve juste devant lui)
exemple
@example, Étonnamment, GCC donne cet avertissement, mais Clang ne le fait pas.
chris
39

Oui, il existe un certain nombre de modifications qui entraîneront le même code à entraîner un comportement différent entre C ++ 03 et C ++ 11. Les différences de règles de séquençage entraînent des changements intéressants, dont certains comportements auparavant non définis devenant bien définis.

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

Un cas d'angle très intéressant serait de multiples 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 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 clangen mode C ++ 03, il fournit l'avertissement suivant ( voir en direct ):

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

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

                           ^        ~~

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

2. De nouvelles règles de séquencement font i = ++ i + 1; bien défini en C ++ 11

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

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

n'est plus un comportement indéfini en C ++ 11, cela est traité dans le rapport de défauts 637. Règles de séquencement et exemple en désaccord

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

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

int i = 0 ;
++++i ;

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

4. Décalages à gauche légèrement plus sensibles et signés

Les versions ultérieures de C ++ 11 incluent N3485que je lie ci-dessous fixe le comportement indéfini de décaler un bit dans ou après le bit de signe . Ceci est également couvert dans le rapport de défauts 1457 . Howard Hinnant a commenté la signification de ce changement dans le fil de discussion sur Est-ce que le décalage vers la gauche (<<) est un comportement négatif entier non défini en C ++ 11? .

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

C ++ 11 a introduit des fonctions constexpr 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 de constante de temps de compilation sont autorisées.

tandis que C ++ 03 n'a pas la fonctionnalité constexpr , nous n'avons pas à utiliser explicitement le mot clé constexpr car la bibliothèque standard fournit de nombreuses fonctions en C ++ 11 comme constexpr . Par exemple std :: numeric_limits :: min . Ce qui peut conduire à des comportements différents, par exemple:

#include <limits>

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

L'utilisation clangen C ++ 03 entraînera xun tableau de longueur variable, qui est une 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] ;
         ^

en C ++ 11 std::numeric_limits<unsigned int>::min()+2 est une expression de constante de temps de compilation et 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

Étant donné qu'en C ++ 11, le destructeur défini par l'utilisateur a une noexcept(true)spécification implicite 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::terminatemais 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 bien décrit dans Pourquoi std :: sort n'accepte pas les classes Compare 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 clangautorise actuellement ce code en mode C ++ 03 avec un avertissement, sauf si vous utilisez un -pedantic-errorsindicateur, ce qui est un peu icky, voyez-le en direct .

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

Utiliser >>pour fermer plusieurs modèles n'est plus mal formé mais peut conduire à du code avec des résultats différents en C ++ 03 et C + 11. L'exemple ci-dessous est tiré des supports à angle droit et de la compatibilité descendante :

#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 change 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 des résultats différents 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étrécissement des conversions dans les initialiseurs globaux

En C ++ 11, une conversion de rétrécissement dans les initialiseurs d'agrégation est mal formée et il semble que gcccela soit possible en C ++ 11 et C ++ 03 bien qu'elle fournisse un avertissement par défaut en C ++ 11:

int x[] = { 2.0 };

Ceci est traité dans le projet de section standard d' 8.5.4 initialisation de la liste C ++ 11 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 (c'est moi qui souligne ):

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 de rétrécissement (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 le projet de section standard annex C.2 C ++ 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 des chaînes littérales définies par l'utilisateur [...] Auparavant, le n ° 1 aurait consisté en deux jetons de prétraitement distincts et la macro _x aurait été étendue. Dans la présente Norme internationale, # 1 consiste en un seul jeton de prétraitement, de sorte que la macro n'est pas développée.

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

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

  • Modifier la classe de base de std :: ios_base :: failure [...] std :: ios_base :: failure n'est plus dérivé directement de std :: exception, mais est désormais dérivé de std :: system_error, qui à son tour est dérivé de std :: runtime_error. Un code C ++ 2003 valide qui suppose que std :: ios_base :: failure est dérivé directement de std :: exception peut s'exécuter différemment dans la présente Norme internationale.

Shafik Yaghmour
la source
La plupart des exemples se limitent donc au fait qu'un comportement auparavant indéfini est désormais bien défini?
MatthiasB
@MatthiasB 2, 3 et 4 sont à ce sujet, donc à ce stade, ils ne sont plus la majorité des exemples. Je doute que je trouve beaucoup plus d'exemples de comportement non définis, alors que j'ajoute plus, ils deviendront un ensemble plus petit.
Shafik Yaghmour
Eh bien, le comportement # 1 n'est pas spécifié, donc je le considérerais comme un comportement non défini (au moins, vous ne pouvez pas vous attendre à obtenir un résultat spécifique avec c ++ 03, maintenant avec c ++ 11 vous pouvez), # 5 utilise un non extension standard de c ++. Mais je suppose que tu as raison. Plus vous la recherchez, plus vous trouverez d'exemples définis dans les deux normes mais produisant des résultats différents.
MatthiasB
@MatthiasB oui, les comportements non spécifiés et non définis ont des résultats indésirables. En ce qui concerne les extensions, Linux dépend d'un certain nombre d'extensions gcc que nous devrions supposer dans le monde réel, elles importent. Je ne m'attendais pas à trouver autant d'exemples lorsque j'ai répondu à cette question pour la première fois.
Shafik Yaghmour
35

Un changement potentiellement dangereux en arrière incompatible est dans les constructeurs de conteneurs de séquence tels que std::vector, spécifiquement dans la surcharge spécifiant la taille initiale. Là où en C ++ 03, ils ont copié un élément construit par défaut, en C ++ 11 ils ont construit chacun par défaut.

Considérez cet exemple (en utilisant boost::shared_ptrpour 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 la taille uniquement", comme ceci (arguments d'allocateur omis pour des raisons de concision):

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

Cela sera toujours copié prototypedans les sizetemps du conteneur . Lorsqu'il est appelé avec un seul argument, il crée donc des sizecopies d'un élément construit par défaut.

En C ++ 11, cette signature de constructeur a été supprimée et remplacée par ces deux surcharges:

container(size_type size);

container(size_type size, const value_type &prototype);

Le second fonctionne comme auparavant, créant des sizecopies duprototype élément. Cependant, le premier (qui gère désormais les appels avec uniquement l'argument taille spécifié) construit par défaut 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 à déplacement uniquement. Mais c'est un changement de rupture néanmoins, et rarement documenté à ce sujet.

Angew n'est plus fier de SO
la source
3
Bien que ce soit évidemment un changement de rupture, je préfère le comportement C ++ 11. Je m'attends à ce que cela se traduise par la dequedétention de dix widgets distincts, et non pas dix widgets partageant la même ressource.
Agentlien
19

Le résultat d'un échec de lecture à partir d'un std::istreama changé. CppReference le résume bien:

Si l'extraction échoue (par exemple, si une lettre a été entrée alors qu'un chiffre est attendu), elle valuen'est pas modifiée et failbitest définie.(jusqu'à C ++ 11)

Si l'extraction échoue, zéro est écrit valueet failbitest défini. Si l'extraction aboutit à une valeur trop grande ou trop petite pour tenir value, std::numeric_limits<T>::max()ou std::numeric_limits<T>::min()est écrite et l' failbitindicateur 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 particulièrement une bonne pratique mais 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 non défini.

Anton Golov
la source
4
Vous pourriez ajouter qu'en C ++ 03, on aurait pu utiliser ce comportement standardisé pour fournir une valeur par défaut, comme dans int x = 1, y = 1; cin >> x >> y; cout << x*y;. Avec C ++ 03, cela aurait produit correctement xquand aucun ne ypouvait être lu.
cmaster - réintègre monica le
15

Ce thread Quelles différences, le cas échéant, entre C ++ 03 et C ++ 0x peuvent être détectées au moment de l'exécution ont des exemples (copiés à partir de ce thread) pour déterminer les différences de langage, par exemple en exploitant l'effondrement des références 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); 
}

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

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

Tirages:

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

Voir le résultat sur Coliru

StackedCrooked
la source