¿Puede el código C ++ ser válido tanto en C ++ 03 como en C ++ 11 pero hacer cosas diferentes?

299

¿Es posible que el código C ++ se ajuste tanto al estándar C ++ 03 como al estándar C ++ 11 , pero hace diferentes cosas según el estándar que se está compilando?

Erik Sjölund
fuente
26
Estoy bastante seguro de que autopodría dar lugar a una situación como esta
OMGtechy
8
Si. Un ejemplo es >>cuando se usa en una plantilla. Puede llegar a una situación en la que pueda compilar para ambos estándares. Otro para el que estoy seguro sería fácil encontrar cambios es en la inicialización.
Chris
55
Aquí hay un buen artículo sobre la >> situación: gustedt.wordpress.com/2013/12/15/…
chris
66
@OMGtechy: No creo que auto pueda causar esto. Con el significado anterior, una autodeclaración requiere un nombre de tipo; con el nuevo significado, no se permite un nombre de tipo.
Keith Thompson el
2
¿Cómo es abierto? Usted mismo señaló a través de otra pregunta que la respuesta a esta pregunta es "sí, aquí hay un ejemplo de cómo". Hay una respuesta muy definitiva a la pregunta, como usted mismo señaló.
jalf

Respuestas:

284

La respuesta es un sí definitivo. En el lado positivo hay:

  • El código que anteriormente copiaba objetos implícitamente ahora los moverá implícitamente cuando sea posible.

En el lado negativo, se enumeran varios ejemplos en el apéndice C de la norma. Aunque hay muchos más negativos que positivos, es mucho menos probable que ocurra cada uno de ellos.

Literales de cadena

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

y

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

Escriba conversiones de 0

En C ++ 11, solo los literales son constantes de puntero entero nulo:

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

Resultados redondeados después de la división entera y módulo

En C ++ 03, se permitió al compilador redondear hacia 0 o hacia infinito negativo. En C ++ 11 es obligatorio redondear hacia 0

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

Espacios en blanco entre llaves de cierre de plantilla anidadas >> vs>>

Dentro de una especialización o instanciación >>, en su lugar, se podría interpretar como un desplazamiento a la derecha en C ++ 03. Sin embargo, es más probable que esto rompa el código existente: (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);
}

El operador newahora puede lanzar otras excepciones 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
}

Los destructores declarados por el usuario tienen un ejemplo de especificación de excepción implícita de ¿Qué cambios importantes se introducen en C ++ 11?

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

size() de contenedores ahora se requiere para ejecutarse en O (1)

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

std::ios_base::failureno deriva directamente de std::exceptionmás

Mientras que la clase base directa es nueva, std::runtime_errorno lo es. Así:

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";
}
ejemplo
fuente
11
Niza, +1. Otra es que un usuario declarado destructor ahora está implícitamente, noexecpt(true)por lo que throwen un destructor ahora se llamará std::terminate. ¡Pero espero que cualquiera que haya escrito dicho código esté contento con esto!
typ1232
44
Pero std :: system_error en sí mismo se deriva (indirectamente) de std :: exception, por lo que catch (std::exception &)aún se captura std::ios_base::failure.
user2665887
@ user2665887 tienes razón. todavía puede influir en el comportamiento de un programa, pero no puedo pensar en un ejemplo mínimo en este momento.
ejemplo
44
Estoy súper confundido, ya que lo que dices sobre operator newes preciso (ahora puede arrojar std::bad_array_new_length), pero tu ejemplo no muestra eso en absoluto. El código que muestra es el mismo en C ++ 03 y C ++ 11 AFAIK.
Mooing Duck
2
El otro lado de la lista :: el tamaño es O (1) es que el empalme ahora es O (n)
Tony Delroy
56

Le indico este artículo y el seguimiento , que tiene un buen ejemplo de cómo >>puede cambiar el significado de C ++ 03 a C ++ 11 sin dejar de compilar ambos.

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 clave es la línea main, que es una expresión.

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

Felicidades, dos resultados diferentes para la misma expresión. De acuerdo, el C ++ 03 apareció con un formulario de advertencia Clang cuando lo probé.

Chris
fuente
es raro que no requiere typenamede ::twoen C ++ versión 03
Zahir
3
Agradable, hacer que se reduzca para evaluar según trueo falsepara los diferentes estándares. Tal vez podríamos usarlo como una prueba de función </joke>
cmaster - restablecer monica
@zahir, no es un tipo, solo un valor.
Chris
bueno, las opciones adecuadas de cmdline advierten sobre esto ( warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]), pero sigue siendo un buen ejemplo de cómo el ::operador ambiguo cambia de significado (ya sea refiriéndose al alcance global o desreferenciando al que está justo delante de él)
ejemplo
@ejemplo, sorprendentemente, GCC da esa advertencia, pero Clang no.
Chris
39

Sí, hay varios cambios que harán que el mismo código dé como resultado un comportamiento diferente entre C ++ 03 y C ++ 11. Las diferencias en las reglas de secuencia hacen algunos cambios interesantes que incluyen un comportamiento previamente indefinido que se vuelve bien definido.

1. mutaciones múltiples de la misma variable dentro de una lista de inicializador

Un caso de esquina muy interesante sería múltiples mutaciones de la misma variable dentro de una lista de inicializadores, por ejemplo:

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

    return 0 ;
}

Tanto en C ++ 03 como en C ++ 11 esto está bien definido, pero el orden de evaluación en C ++ 03 no está especificado, pero en C ++ 11 se evalúan en el orden en que aparecen . Entonces, si compilamos usando el clangmodo C ++ 03, nos da la siguiente advertencia ( verlo en vivo ):

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

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

                           ^        ~~

pero no proporciona una advertencia en C ++ 11 ( verlo en vivo ).

2. Las nuevas reglas de secuenciación hacen que i = ++ i + 1; bien definido en C ++ 11

Las nuevas reglas de secuencia adoptadas después de C ++ 03 significan que:

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

ya no es un comportamiento indefinido en C ++ 11, esto está cubierto en el informe de defectos 637. Las reglas de secuencia y el ejemplo no están de acuerdo

3. Las nuevas reglas de secuencia también hacen que ++++ i; bien definido en C ++ 11

Las nuevas reglas de secuencia adoptadas después de C ++ 03 significan que:

int i = 0 ;
++++i ;

ya no es un comportamiento indefinido en C ++ 11.

4. Desplazamientos a la izquierda firmados ligeramente más sensibles

Los borradores posteriores de C ++ 11 incluyen N3485el enlace a continuación que corrigió el comportamiento indefinido de cambiar un bit dentro o más allá del bit de signo . Esto también está cubierto en el informe de defectos 1457 . Howard Hinnant comentó sobre la importancia de este cambio en el hilo de ¿Es el desplazamiento a la izquierda (<<) un comportamiento entero indefinido negativo en C ++ 11? .

5. las funciones constexpr pueden tratarse como expresiones constantes de tiempo de compilación en C ++ 11

C ++ 11 introdujo funciones constexpr que:

El especificador constexpr declara que es posible evaluar el valor de la función o variable en tiempo de compilación. Dichas variables y funciones se pueden usar donde solo se permiten expresiones constantes de tiempo de compilación.

Si bien C ++ 03 no tiene la función constexpr , no tenemos que usar explícitamente la palabra clave constexpr ya que la biblioteca estándar proporciona muchas funciones en C ++ 11 como constexpr . Por ejemplo std :: numeric_limits :: min . Lo que puede conducir a un comportamiento diferente, por ejemplo:

#include <limits>

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

El uso clangen C ++ 03 hará xque sea una matriz de longitud variable, que es una extensión y generará la siguiente advertencia:

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

mientras que en C ++ 11 std::numeric_limits<unsigned int>::min()+2es una expresión constante de tiempo de compilación y no requiere la extensión VLA.

6. En C ++ 11 las excepciones de excepción se generan implícitamente para sus destructores

Dado que en C ++ 11 el destructor definido por el usuario tiene una noexcept(true)especificación implícita como se explica en los destructores noexcept , significa que el siguiente programa:

#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 llamará std::terminatepero se ejecutará con éxito en C ++ 03.

7. En C ++ 03, los argumentos de plantilla no pueden tener un enlace interno

Esto se cubre muy bien en Por qué std :: sort no acepta Comparar clases declaradas dentro de una función . Por lo tanto, el siguiente código no debería funcionar 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;
    }
}

pero actualmente clangpermite este código en modo C ++ 03 con una advertencia, a menos que use la -pedantic-errorsmarca, lo cual es un poco asqueroso, véalo en vivo .

8. >> ya no está mal formado al cerrar varias plantillas

El uso >>para cerrar varias plantillas ya no está mal formado, pero puede generar código con resultados diferentes en C ++ 03 y C + 11. El siguiente ejemplo se toma de los corchetes de ángulo recto y la compatibilidad con versiones anteriores :

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

y el resultado en C ++ 03 es:

0
3

y en C ++ 11:

0
0

9. C ++ 11 cambia algunos de los constructores std :: vector

El código ligeramente modificado de esta respuesta muestra que usando el siguiente constructor de std :: vector :

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

produce resultados diferentes en C ++ 03 y 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. Reducción de conversiones en inicializadores agregados

En C ++ 11, una conversión de reducción en los inicializadores agregados está mal formada y parece que gccpermite esto tanto en C ++ 11 como en C ++ 03, aunque proporciona una advertencia por defecto en C ++ 11:

int x[] = { 2.0 };

Esto está cubierto en el borrador de la sección estándar de C ++ 11 8.5.4 Lista-inicialización párrafo 3 :

La inicialización de lista de un objeto o referencia de tipo T se define de la siguiente manera:

y contiene la siguiente viñeta ( énfasis mío ):

De lo contrario, si T es un tipo de clase, se consideran los constructores. Los constructores aplicables se enumeran y el mejor se elige mediante resolución de sobrecarga (13.3, 13.3.1.7). Si se requiere una conversión de reducción (ver más abajo) para convertir cualquiera de los argumentos, el programa está mal formado

Esta y muchas más instancias están cubiertas en el borrador de la sección estándar annex C.2 C ++ C ++ e ISO C ++ 2003 . También incluye:

  • Nuevos tipos de literales de cadena [...] Específicamente, las macros denominadas R, u8, u8R, u, uR, U, UR o LR no se expandirán cuando estén adyacentes a un literal de cadena, sino que se interpretarán como parte del literal de cadena. . Por ejemplo

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
    
  • Soporte de cadena literal definido por el usuario [...] Anteriormente, el # 1 habría consistido en dos tokens de preprocesamiento separados y la macro _x se habría expandido. En esta Norma Internacional, el # 1 consiste en un solo token de preprocesamiento, por lo que la macro no se expande.

    #define _x "there"
    "hello"_x // #1
    
  • Especifique el redondeo para resultados de código entero /% [...] 2003 que usa división entera redondea el resultado hacia 0 o hacia infinito negativo, mientras que esta Norma Internacional siempre redondea el resultado hacia 0.

  • La complejidad de las funciones miembro de size () ahora es constante [...] Algunas implementaciones de contenedores que cumplen con C ++ 2003 pueden no cumplir con los requisitos de size () especificados en esta Norma Internacional. Ajustar contenedores como std :: list a los requisitos más estrictos puede requerir cambios incompatibles.

  • Cambiar la clase base de std :: ios_base :: failure [...] std :: ios_base :: failure ya no se deriva directamente de std :: excepción, sino que ahora se deriva de std :: system_error, que a su vez se deriva de std :: runtime_error. El código válido de C ++ 2003 que asume que std :: ios_base :: failure se deriva directamente de std :: excepción puede ejecutarse de manera diferente en este estándar internacional.

Shafik Yaghmour
fuente
Entonces, ¿la mayoría de los ejemplos se reducen al hecho de que el comportamiento previamente indefinido ahora está bien definido?
MatthiasB
@MatthiasB 2, 3 y 4 tratan sobre esto, por lo que en este punto ya no son la mayoría de los ejemplos. Dudo que encuentre muchos más ejemplos de comportamiento indefinido, así que a medida que agregue más, se convertirán en un conjunto más pequeño.
Shafik Yaghmour
Bueno, el comportamiento n. ° 1 no está especificado, por lo que lo consideraría un comportamiento indefinido (al menos no puede esperar obtener un resultado específico con c ++ 03, ahora con c ++ 11 puede), el n. ° 5 usa un extensión estándar de c ++. Pero supongo que tienes razón. Cuanto más lo busque, más ejemplos encontrará que están definidos en ambos estándares pero producen resultados diferentes.
MatthiasB
@MatthiasB sí, tanto el comportamiento no especificado como el indefinido tienen resultados indeseables. En cuanto a las extensiones, considerar que Linux depende de una serie de extensiones de gcc que debemos asumir en el mundo real que importan. No esperaba encontrar tantos ejemplos cuando respondí esta pregunta por primera vez.
Shafik Yaghmour
35

Un cambio potencialmente incompatible con versiones anteriores es peligroso en los constructores de contenedores de secuencia, como std::vector, específicamente, en la sobrecarga que especifica el tamaño inicial. Donde en C ++ 03, copiaron un elemento construido por defecto, en C ++ 11 construyeron por defecto cada uno.

Considere este ejemplo (utilizando boost::shared_ptrpara que sea válido 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 Ejemplo en vivo

Ejemplo de C ++ 11 Live

La razón es que C ++ 03 especificó una sobrecarga para "especificar el tamaño y el elemento prototipo" y "especificar solo el tamaño", de esta manera (los argumentos del asignador se omiten por brevedad):

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

Esto siempre se copiará prototypeen los sizetiempos del contenedor . Cuando se llama con un solo argumento, por lo tanto, creará sizecopias de un elemento construido por defecto.

En C ++ 11, esta firma del constructor se eliminó y se reemplazó con estas dos sobrecargas:

container(size_type size);

container(size_type size, const value_type &prototype);

El segundo funciona como antes, creando sizecopias del prototypeelemento. Sin embargo, el primero (que ahora maneja llamadas con solo el argumento de tamaño especificado) construye por defecto cada elemento individualmente.

Supongo que la razón de este cambio es que la sobrecarga de C ++ 03 no sería utilizable con un tipo de elemento de solo movimiento. Sin embargo, es un cambio innovador, y rara vez se documenta en eso.

Angew ya no está orgulloso de SO
fuente
3
Si bien esto es obviamente un cambio importante, prefiero el comportamiento de C ++ 11. Esperaría que esto dé como resultado dequetener diez widgets separados, no diez widgets que compartan el mismo recurso.
Agentlien
19

El resultado de una lectura fallida de un std::istreamha cambiado. CppReference lo resume muy bien:

Si la extracción falla (por ejemplo, si se ingresó una letra donde se espera un dígito), valuese deja sin modificar y failbitse establece. (hasta C ++ 11)

Si la extracción falla, se escribe cero valuey failbitse establece. Si la extracción da como resultado un valor demasiado grande o demasiado pequeño para ajustarse value, std::numeric_limits<T>::max()o std::numeric_limits<T>::min()se escribe y failbitse establece la marca. (desde C ++ 11)

Esto es principalmente un problema si estás acostumbrado a la nueva semántica y luego tienes que escribir usando C ++ 03. La siguiente no es una práctica particularmente buena, pero está bien definida en C ++ 11:

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

Sin embargo, en C ++ 03, el código anterior utiliza una variable no inicializada y, por lo tanto, tiene un comportamiento indefinido.

Anton Golov
fuente
44
Puede agregar que en C ++ 03 se podría haber utilizado este comportamiento estandarizado para proporcionar un valor predeterminado, como en int x = 1, y = 1; cin >> x >> y; cout << x*y;. Con C ++ 03, esto habría producido correctamente xcuando no se ypudiera leer.
cmaster - reinstalar a monica el
15

Este subproceso Las diferencias, si las hay, entre C ++ 03 y C ++ 0x que se pueden detectar en tiempo de ejecución tiene ejemplos (copiados de ese subproceso) para determinar las diferencias de idioma, por ejemplo explotando el colapso de referencia de 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); 
}

y c ++ 11 que permite tipos locales como parámetros de plantilla:

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
fuente
7

Aquí hay otro ejemplo:

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

Huellas dactilares:

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

Ver el resultado en Coliru

Apilado
fuente