O código C ++ pode ser válido no C ++ 03 e no C ++ 11, mas faz coisas diferentes?

299

É possível que o código C ++ esteja em conformidade com o padrão C ++ 03 e o padrão C ++ 11 , mas faça coisas diferentes dependendo de qual padrão ele está sendo compilado?

Erik Sjölund
fonte
26
Tenho certeza de que autopoderia resultar em uma situação como esta
OMGtechy
8
Sim. Um exemplo é >>quando usado em um modelo. Você pode criar uma situação em que ele pode compilar para os dois padrões. Outro que tenho certeza de que seria fácil encontrar alterações está na inicialização.
Chris13 /
5
Aqui está um artigo agradável na >> situação: gustedt.wordpress.com/2013/12/15/...
chris
6
@OMGtechy: Eu não acho que auto possa causar isso. Com o significado antigo, uma autodeclaração requer um nome de tipo; com o novo significado, um nome de tipo não é permitido.
21414 Keith Thompson #
2
Como é aberto? Você mesmo apontou através de outra pergunta que a resposta a esta pergunta é "sim, aqui está um exemplo de como". Há uma resposta muito definida para a pergunta, como você mesmo apontou.
jalf

Respostas:

284

A resposta é definitivamente sim. No lado positivo, há:

  • O código que anteriormente copiou implicitamente objetos agora os moverá implicitamente quando possível.

No lado negativo, vários exemplos estão listados no apêndice C da norma. Embora existam muito mais negativos do que positivos, cada um deles tem muito menos probabilidade de ocorrer.

Literais de string

#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

Conversões de tipo 0

No C ++ 11, apenas literais são constantes de ponteiro nulo inteiro:

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

Resultados arredondados após divisão e módulo inteiro

No C ++ 03, o compilador foi autorizado a arredondar para 0 ou para o infinito negativo. No C ++ 11, é obrigatório arredondar para 0

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

Espaços em branco entre chaves de fechamento de modelo aninhado >> vs>>

Dentro de uma especialização ou instanciação, >>pode ser interpretado como um deslocamento à direita no C ++ 03. Porém, é mais provável que isso ocorra com o código existente: (em 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);
}

O operador newagora pode lançar outras exceçõesstd::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
}

Os destruidores declarados pelo usuário têm um exemplo de especificação de exceção implícita de Quais alterações recentes são introduzidas no 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 contêineres agora é necessário para executar em O (1)

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

std::ios_base::failurenão deriva diretamente de std::exceptionmais

Enquanto a classe base direta é nova, std::runtime_errornão é. Portanto:

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";
}
exemplo
fonte
11
Bom, +1. Outra é que um usuário declarado destruidor agora é implicitamente, noexecpt(true)portanto, throwem um destruidor agora será chamado std::terminate. Mas espero que qualquer pessoa que tenha escrito esse código fique feliz com isso!
typ1232
4
Mas std :: system_error em si é (indiretamente) derivado de std :: exception, portanto catch (std::exception &)ainda é capturado std::ios_base::failure.
user2665887
@ user2665887 você está certo. ainda pode influenciar o comportamento de um programa, mas não consigo pensar em um exemplo mínimo no momento.
exemplo
4
Estou super confuso, pois o que você diz operator newé preciso (agora pode ser lançado std::bad_array_new_length), mas o seu exemplo não mostra nada disso. O código que você mostra é o mesmo em C ++ 03 e C ++ 11 AFAIK.
achou do
2
O outro lado da lista :: tamanho sendo S (1) é que a emenda é agora O (n)
Tony Delroy
56

Eu aponto para você este artigo e o acompanhamento , que tem um bom exemplo de como >>pode mudar o significado de C ++ 03 para C ++ 11 enquanto ainda compila os dois.

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  
}

A parte principal é a linha main , que é uma expressão.

No C ++ 03:

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

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

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

Em 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

Parabéns, dois resultados diferentes para a mesma expressão. É verdade que o C ++ 03 apresentou um formulário de aviso Clang quando o testei.

chris
fonte
é estranho que ele não requer typenamepara ::twoem C ++ 03 versão
zahir
3
Agradável, resumindo-se à avaliação de trueou falsepara os diferentes padrões. Talvez pudéssemos usá-lo como um teste de recurso </ piada>
cmaster - Reintegrar monica
@zahir, não é um tipo, apenas um valor.
chris
assim, as opções cmdline adequadas alertar sobre isto ( warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]), mas ainda um bom exemplo de como a ambígua ::operador muda significando (seja referindo-se ao escopo global ou dereferencing o pé diretamente antes dele)
exemplo
@exemplo, surpreendentemente, o GCC dá esse aviso, mas Clang não.
Chris13 /
39

Sim, existem várias alterações que farão com que o mesmo código resulte em comportamento diferente entre C ++ 03 e C ++ 11. As diferenças das regras de seqüenciamento fazem algumas mudanças interessantes, incluindo um comportamento indefinido anteriormente se tornando bem definido.

1. múltiplas mutações da mesma variável em uma lista de inicializadores

Um caso de canto muito interessante teria várias mutações da mesma variável em uma lista de inicializadores, por exemplo:

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

    return 0 ;
}

No C ++ 03 e no C ++ 11, isso está bem definido, mas a ordem de avaliação no C ++ 03 não é especificada, mas no C ++ 11 elas são avaliadas na ordem em que aparecem . Portanto, se compilarmos usando clangno modo C ++ 03, forneça o seguinte aviso ( veja ao vivo ):

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

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

                           ^        ~~

mas não fornece um aviso no C ++ 11 ( veja ao vivo ).

2. Novas regras de seqüenciamento tornam i = ++ i + 1; bem definido em C ++ 11

As novas regras de seqüenciamento adotadas após o C ++ 03 significam que:

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

não é mais um comportamento indefinido no C ++ 11, isso é coberto no relatório de defeitos 637. Regras e exemplo de seqüenciamento discordam

3. Novas regras de seqüenciamento também tornam ++++ i; bem definido em C ++ 11

As novas regras de seqüenciamento adotadas após o C ++ 03 significam que:

int i = 0 ;
++++i ;

não é mais um comportamento indefinido no C ++ 11.

4. Deslocamentos à esquerda assinados um pouco mais sensíveis

Os rascunhos posteriores do C ++ 11 incluem o N3485link abaixo, que corrigiu o comportamento indefinido de mudar um bit para o bit de sinal ou ultrapassá-lo . Isso também é coberto no relatório de defeitos 1457 . Howard Hinnant comentou sobre o significado dessa alteração no thread. O deslocamento para a esquerda (<<) é um comportamento indefinido de número inteiro negativo no C ++ 11? .

5. As funções constexpr podem ser tratadas como expressões constantes de tempo de compilação em C ++ 11

O C ++ 11 introduziu funções constexpr que:

O especificador constexpr declara que é possível avaliar o valor da função ou variável em tempo de compilação. Tais variáveis ​​e funções podem ser usadas onde apenas expressões de constante de tempo de compilação são permitidas.

enquanto o C ++ 03 não possui o recurso constexpr , não precisamos usar explicitamente a palavra-chave constexpr, pois a biblioteca padrão fornece muitas funções no C ++ 11 como constexpr . Por exemplo, std :: numeric_limits :: min . O que pode levar a um comportamento diferente, por exemplo:

#include <limits>

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

O uso clangno C ++ 03 causa xuma matriz de comprimento variável, que é uma extensão e gera o seguinte aviso:

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

enquanto em C ++ 11 std::numeric_limits<unsigned int>::min()+2 é uma expressão constante de tempo de compilação e não requer a extensão VLA.

6. No C ++ 11, as exceções de exceção são geradas implicitamente para seus destruidores

Como no destruidor definido pelo usuário do C ++ 11 tem noexcept(true)especificação implícita, conforme explicado em noexcept destructors, isso significa que o seguinte 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";
}

No C ++ 11 será chamado, std::terminatemas será executado com êxito no C ++ 03.

7. No C ++ 03, os argumentos do modelo não podiam ter ligação interna

Isso é abordado muito bem em Por que std :: sort não aceita classes Compare declaradas em uma função . Portanto, o código a seguir não deve funcionar no 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;
    }
}

mas atualmente clangpermite esse código no modo C ++ 03 com um aviso, a menos que você use -pedantic-errorssinalizador, que é meio nojento, veja ao vivo .

8. >> não está mais mal formado ao fechar vários modelos

Usar >>para fechar vários modelos não está mais mal formado, mas pode levar ao código com resultados diferentes em C ++ 03 e C + 11. O exemplo abaixo é retirado de colchetes angulares retos e compatibilidade com versões 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';
}

e o resultado em C ++ 03 é:

0
3

e em C ++ 11:

0
0

9. O C ++ 11 altera alguns dos construtores std :: vector

O código levemente modificado desta resposta mostra que, usando o seguinte construtor de std :: vector :

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

produz resultados diferentes em 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. Restringir conversões em inicializadores agregados

No C ++ 11, uma conversão restrita nos inicializadores agregados é mal formada e parece gccpermitir isso no C ++ 11 e no C ++ 03, embora forneça um aviso por padrão no C ++ 11:

int x[] = { 2.0 };

Isso é coberto no rascunho da seção padrão do C ++ 11, parágrafo 3 de 8.5.4 inicialização da lista :

A inicialização da lista de um objeto ou referência do tipo T é definida da seguinte maneira:

e contém o seguinte marcador ( ênfase minha ):

Caso contrário, se T for um tipo de classe, os construtores serão considerados. Os construtores aplicáveis ​​são enumerados e o melhor é escolhido através da resolução de sobrecarga (13.3, 13.3.1.7). Se uma conversão restrita (veja abaixo) for necessária para converter qualquer um dos argumentos, o programa será mal formado

Isto e muito mais exemplo são abrangidos no projecto de C ++ padrão secção annex C.2 C ++ e ISO C ++ 2003 . Também inclui:

  • Novos tipos de literais de cadeia [...] Especificamente, macros denominadas R, u8, u8R, u, uR, U, UR ou LR não serão expandidas quando adjacentes a uma literal de cadeia, mas serão interpretadas como parte da literal de cadeia. . Por exemplo

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
  • Suporte a cadeia literal definido pelo usuário [...] Anteriormente, o nº 1 consistiria em dois tokens de pré-processamento separados e a macro _x teria sido expandida. Nesse padrão internacional, o número 1 consiste em um único tokens de pré-processamento, para que a macro não seja expandida.

    #define _x "there"
    "hello"_x // #1
  • Especifique o arredondamento para resultados do código inteiro / e% [...] 2003 que usa divisão inteira arredonda o resultado para 0 ou para o infinito negativo, enquanto que este Padrão Internacional sempre arredonda o resultado para 0.

  • Complexidade das funções de membro size () agora constantes [...] Algumas implementações de contêineres que estão em conformidade com o C ++ 2003 podem não estar em conformidade com os requisitos de tamanho () especificados nesta Norma. Ajustar contêineres como std :: list aos requisitos mais rígidos pode exigir alterações incompatíveis.

  • Alterar classe base de std :: ios_base :: fail [...] std :: ios_base :: fail não é mais derivado diretamente de std :: exception, mas agora é derivado de std :: system_error, que por sua vez é derivado de std :: runtime_error. O código C ++ 2003 válido que assume que std :: ios_base :: fault é derivado diretamente de std :: exception pode ser executado de maneira diferente neste Padrão Internacional.

Shafik Yaghmour
fonte
Portanto, a maioria dos exemplos se restringe ao fato de que o comportamento indefinido anteriormente está agora bem definido?
perfil
@ MatthiasB 2, 3 e 4 são sobre isso, então, neste momento, eles não são mais a maioria dos exemplos. Duvido que encontre muitos outros exemplos de comportamento indefinidos, portanto, à medida que adiciono mais, eles se tornarão um conjunto menor.
Shafik Yaghmour
Bem, o comportamento número 1 não é especificado, então eu o consideraria como comportamento indefinido (pelo menos você não pode esperar obter um resultado específico com o c ++ 03, agora com o c ++ 11 você pode), o número 5 usa um método não- extensão padrão do c ++. Mas acho que você está certo. Quanto mais você procurar, mais exemplos você encontrará, os quais são definidos nos dois padrões, mas produzem resultados diferentes.
perfil
@MatthiasB sim, o comportamento não especificado e o indefinido têm resultados indesejáveis. Quanto às extensões, considerar o Linux depende de várias extensões do gcc que devemos assumir no mundo real que são importantes. Eu não esperava encontrar tantos exemplos quando respondi a essa pergunta pela primeira vez.
Shafik Yaghmour
35

Uma mudança potencialmente perigosa e incompatível com versões anteriores está nos construtores de contêineres de sequência, como std::vector, especificamente, na sobrecarga que especifica o tamanho inicial. Onde no C ++ 03, eles copiaram um elemento construído por padrão, no C ++ 11 eles construíram o padrão de cada um.

Considere este exemplo (usando boost::shared_ptrC ++ 03 válido):

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

Exemplo do C ++ 03 Live

Exemplo do C ++ 11 Live

O motivo é que o C ++ 03 especificou uma sobrecarga para "especificar tamanho e elemento de protótipo" e "especificar apenas tamanho", assim (argumentos de alocador omitidos por questões de brevidade):

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

Isso sempre será copiado prototypepara os sizehorários do contêiner . Quando chamado com apenas um argumento, ele cria sizecópias de um elemento construído por padrão.

No C ++ 11, essa assinatura do construtor foi removida e substituída por essas duas sobrecargas:

container(size_type size);

container(size_type size, const value_type &prototype);

O segundo funciona como antes, criando sizecópias doprototype elemento. No entanto, o primeiro (que agora lida com chamadas apenas com o argumento de tamanho especificado) constrói por padrão cada elemento individualmente.

Meu palpite pelo motivo dessa alteração é que a sobrecarga do C ++ 03 não seria utilizável com um tipo de elemento somente para movimentação. Mas é uma mudança radical, no entanto, e raramente documentada nisso.

Angew não está mais orgulhoso de SO
fonte
3
Embora essa seja obviamente uma mudança radical, prefiro o comportamento do C ++ 11. Eu esperaria que isso resultasse em dequemanter dez widgets separados, não dez widgets compartilhando o mesmo recurso.
precisa saber é o seguinte
19

O resultado de uma leitura com falha de um std::istreamfoi alterado. CppReference resume bem:

Se a extração falhar (por exemplo, se uma letra foi inserida onde um dígito é esperado), não valueé modificada e failbité definida. (até C ++ 11)

Se a extração falhar, zero será gravado valuee failbitdefinido. Se a extração resultar no valor muito grande ou muito pequeno para caber value, std::numeric_limits<T>::max()ou std::numeric_limits<T>::min()for gravada e o failbitsinalizador estiver definido. (desde C ++ 11)

Isso é principalmente um problema se você está acostumado à nova semântica e precisa escrever usando o C ++ 03. O seguinte não é particularmente uma boa prática, mas está bem definido no C ++ 11:

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

No entanto, no C ++ 03, o código acima usa uma variável não inicializada e, portanto, tem um comportamento indefinido.

Anton Golov
fonte
4
Você pode adicionar que, no C ++ 03, alguém poderia ter usado esse comportamento padronizado para fornecer um valor padrão, como em int x = 1, y = 1; cin >> x >> y; cout << x*y;. Com o C ++ 03, isso seria produzido corretamente xquando não ypudesse ser lido.
cmaster - reinstate monica
15

Esta discussão Quais diferenças, se houver alguma, entre C ++ 03 e C ++ 0x podem ser detectadas em tempo de execução têm exemplos (copiados desse encadeamento) para determinar diferenças de idioma, por exemplo, explorando o recolhimento de referência do 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 permitindo tipos locais como parâmetros de modelo:

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

Aqui está outro exemplo:

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

Impressões:

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

Veja o resultado em Coliru

StackedCrooked
fonte