Atingindo a compatibilidade com o C ++ 11

12

Eu trabalho em um aplicativo de software grande que deve ser executado em várias plataformas. Algumas dessas plataformas oferecem suporte a alguns recursos do C ++ 11 (por exemplo, MSVS 2010) e outras não (por exemplo, GCC 4.3.x). Espero que essa situação continue por vários anos (meu melhor palpite: 3-5 anos).

Dado isso, gostaria de configurar uma interface de compatibilidade para que (na medida do possível) as pessoas possam escrever código C ++ 11 que ainda será compilado com compiladores mais antigos com um mínimo de manutenção. No geral, o objetivo é minimizar o número de # ifdef o máximo possível e, ao mesmo tempo, habilitar a sintaxe / recursos básicos do C ++ 11 nas plataformas que os suportam e fornecer emulação nas plataformas que não o fazem.

Vamos começar com std :: move (). A maneira mais óbvia de obter compatibilidade seria colocar algo assim em um arquivo de cabeçalho comum:

#if !defined(HAS_STD_MOVE)
namespace std { // C++11 emulation
  template <typename T> inline T& move(T& v) { return v; }
  template <typename T> inline const T& move(const T& v) { return v; }
}
#endif // !defined(HAS_STD_MOVE)

Isso permite que as pessoas escrevam coisas como

std::vector<Thing> x = std::move(y);

... com impunidade. Ele faz o que eles querem no C ++ 11 e faz o melhor que pode no C ++ 03. Quando finalmente eliminamos o último dos compiladores C ++ 03, esse código pode permanecer como está.

No entanto, de acordo com o padrão, é ilegal injetar novos símbolos no stdespaço para nome. Essa é a teoria. Minha pergunta é: na prática, existe algum mal em fazer isso como uma maneira de obter compatibilidade futura?

mcmcc
fonte
1
O Boost já fornece bastante disso e já possui código para usar novos recursos quando / onde estiver disponível; portanto, você poderá usar apenas o que o Boost fornece e concluir o processo. Obviamente, existem limitações - a maioria dos novos recursos foi adicionada especificamente porque as soluções baseadas em bibliotecas não são adequadas.
perfil completo de Jerry Coffin
Sim, estou pensando especificamente sobre os recursos que podem ser implementados no nível da biblioteca, não sobre as mudanças sintáticas. O Boost realmente não trata da questão da compatibilidade direta (direta). A menos que eu estou faltando alguma coisa ...
mcmcc
O Gcc 4.3 já possui vários recursos do C ++ 11, sendo as referências ao Rvalue provavelmente as mais importantes.
Jan Hudec
@JanHudec: Você está certo. Pobre exemplo. De qualquer forma, existem outros compiladores que definitivamente não suportam a sintaxe (por exemplo, qualquer versão do compilador C ++ da IBM que tenhamos).
precisa saber é

Respostas:

9

Eu tenho trabalhado por um bom tempo em manter um nível de compatibilidade com versões anteriores e anteriores em meus programas C ++, até que finalmente tive que criar um kit de ferramentas de biblioteca , que eu estou preparando para o lançamento já foi lançado. Em geral, desde que você aceite que não será "perfeito" com compatibilidade direta nem nos recursos (algumas coisas simplesmente não podem ser emuladas para frente) nem na sintaxe (você provavelmente precisará usar macros, espaços de nomes alternativos para algumas coisas), então está tudo pronto.

Há muitos recursos que podem ser emulados no C ++ 03 em um nível suficiente para uso prático - e sem todo o aborrecimento que vem com, por exemplo: Boost. Heck, mesmo a proposta de padrões C ++ para nullptrsugere um backport C ++ 03. E há o TR1, por exemplo, para tudo o que é C ++ 11, mas tivemos visualizações há anos. Além disso, alguns recursos do C ++ 14 , como variantes de asserção, functores transparentes e optional podem ser implementados no C ++ 03!

As únicas duas coisas que sei que não podem ser absolutamente suportadas são modelos constexpr e variáveis.

Com relação a toda a questão de adicionar coisas ao namespace std, minha opinião é que não importa - de maneira alguma. Pense no Boost, uma das bibliotecas C ++ mais importantes e relevantes, e sua implementação do TR1: Boost.Tr1. Se você deseja melhorar o C ++, torne-o compatível com o C ++ 11 e, por definição, está transformando-o em algo que não é o C ++ 03; portanto, bloquear-se sobre um padrão que você pretende evitar ou deixar para trás é , simplesmente, contraproducente. Os puristas reclamam, mas, por definição, não é necessário se preocupar com eles.

Claro, só porque você não seguirá o (03) Padrão, afinal, não significa que você não pode tentar ou que iria alegremente sair por aí quebrando-o. Essa não é a questão. Contanto que você mantenha um controle muito cuidadoso sobre o que é adicionado ao stdespaço para nome e tenha um controle dos ambientes em que seu software é usado (por exemplo: faça testes!), Não deve haver nenhum dano intratável. Se possível, defina tudo em um espaço para nome separado e adicione apenas usingdiretivas ao espaço para nome, stdpara que você não adicione nada além do que "absolutamente" precisa entrar. Qual, IINM, é mais ou menos o que o Boost.TR1 faz.


Atualização (2013) : conforme a solicitação da pergunta original e vendo alguns dos comentários aos quais não posso adicionar devido à falta de repetição, aqui está uma lista dos recursos C ++ 11 e C ++ 14 e seu grau de portabilidade para C ++ 03:

  • nullptr: totalmente implementável, considerando o backport oficial do Comitê; você provavelmente precisará fornecer também algumas especializações type_traits para que seja reconhecido como um tipo "nativo".
  • forward_list: totalmente implementável, embora o suporte ao alocador dependa do que a implmenentação do Tr1 pode oferecer.
  • Novos algoritmos (partition_copy, etc): totalmente implementável.
  • Construções de contêineres a partir de sequências de chaves (por exemplo:) vector<int> v = {1, 2, 3, 4};: totalmente implementáveis, embora mais elaboradas do que se gostaria.
  • static_assert: quase totalmente implementável quando implementado como uma macro (você só precisa ter cuidado com vírgulas).
  • unique_ptr: quase totalmente implementável, mas você também precisará de suporte para chamar o código (para armazená-lo em contêineres, etc); veja o abaixo embora.
  • rvalue-reference: quase totalmente implementável, dependendo de quanto você espera obter deles (por exemplo: Boost Move).
  • Para cada iteração: quase totalmente implementável, a sintaxe será um pouco diferente.
  • usando funções locais como argumentos (por exemplo: transformação): quase totalmente implementável, mas a sintaxe será diferente o suficiente - por exemplo, funções locais não são definidas no site de chamada, mas antes.
  • operadores de conversão explícita: implementável em níveis práticos (tornando a conversão explícita), consulte " cast_cast "do Imperfect C ++ ; mas a integração com recursos de linguagem como static_cast<>pode ser quase impossível.
  • encaminhamento de argumento: implementável em níveis práticos, conforme mencionado acima em rvalue-reference, mas você precisará fornecer N sobrecargas às suas funções usando argumentos encaminhados.
  • move: implementável em níveis práticos (veja os dois acima). Obviamente, você precisaria usar contêineres e objetos modificadores para lucrar com isso.
  • Alocadores com escopo definido: Não é realmente implementável, a menos que sua implementação do Tr1 possa ajudá-lo.
  • tipos de caracteres multibyte: Não é realmente implementável, a menos que o Tr1 possa dar suporte a você. Mas, para o objetivo pretendido, é melhor contar com uma biblioteca projetada especificamente para lidar com o assunto, como a UTI, mesmo se estiver usando C ++ 11.
  • Listas de argumentos variáveis: implementáveis ​​com alguns aborrecimentos, preste atenção ao encaminhamento de argumentos.
  • noexcept: depende dos recursos do seu compilador.
  • Novas autosemântica e decltype: depende de características do seu compilador - por exemplo .: __typeof__.
  • tipos de tamanho inteiro ( int16_t, etc): depende dos recursos do seu compilador - ou você pode delegar no Portable stdint.h.
  • Atributos de tipo: depende dos recursos do seu compilador.
  • Lista de inicializadores: não implementável ao meu conhecimento; no entanto, se você deseja inicializar contêineres com sequências, consulte o item acima em "construções de contêineres".
  • Alias ​​de modelo: Não é implemenável ao meu conhecimento, mas é um recurso desnecessário de qualquer maneira, e tivemos ::typemodelos para sempre
  • Modelos variáveis: Não implementável ao meu conhecimento; o argumento close é template padrão, o que requer N especializações etc.
  • constexpr: Não implementável ao meu conhecimento.
  • Inicialização uniforme: Não implementável ao meu conhecimento, mas a inicialização padrão garantida do construtor pode ser implementada, inicializada pelo valor de ala Boost.
  • C ++ 14 dynarray: totalmente implementável.
  • C ++ 14 optional<>: quase totalmente implementável, desde que o seu compilador C ++ 03 suporte configurações de alinhamento.
  • Funtores transparentes C ++ 14: quase totalmente implementáveis, mas o código do seu cliente provavelmente precisará usar explicitamente, por exemplo: .: std::less<void>para fazê-lo funcionar.
  • C ++ 14 novas variantes de asserção (como assure): totalmente implementável se você deseja assertivas, quase totalmente implementável se você deseja ativar arremessos.
  • Extensões de tupla C ++ 14 (obter elemento de tupla por tipo): totalmente implementável, e você pode até conseguir compilar com os casos exatos descritos na proposta de recurso.

(Isenção de responsabilidade: vários desses recursos são implementados na minha biblioteca de backports C ++ que eu vinculei acima, então acho que sei do que estou falando quando digo "totalmente" ou "quase totalmente".)

Luis Machuca
fonte
6

Isso é fundamentalmente impossível. Considere std::unique_ptr<Thing>. Se fosse possível emular referências de rvalue como uma biblioteca, não seria um recurso de idioma.

DeadMG
fonte
1
Eu disse "em qualquer grau possível". Claramente, alguns recursos terão que ser deixados para trás # ifdef's ou não serão utilizados. std :: move () é aquele em que você pode suportar a sintaxe (embora não a funcionalidade).
Mcmcc #
2
Na verdade, a proposta de referências de rvalue menciona uma solução baseada em biblioteca!
Jan Hudec
Mais especificamente, é possível implementar a semântica de movimentação para determinada classe no C ++ 03, por isso deve ser possível defini std::unique_ptr-lo, mas há alguns outros recursos de referências de rvalue que não podem ser implementados no C ++ 03, portanto, std::forwardnão é possível. A outra coisa é que std::unique_ptrisso não será útil, porque as coleções não usarão a semântica de movimentação, a menos que você as substitua todas.
Jan Hudec
@ JanHudec: Não é possível definir unique_ptr. Olhe para as falhas de auto_ptr. unique_ptré praticamente o exemplo de livro didático de uma classe cuja semântica foi fundamentalmente ativada pelo recurso de idioma.
DeadMG
@DeadMG: Não, não unique_ptrfoi isso que foi fundamentalmente ativado pelo recurso de idioma. Não seria muito útil sem esse recurso. porque sem o encaminhamento perfeito, não seria utilizável em muitos casos, e o encaminhamento perfeito exige esse recurso.
Jan Hudec
2
  1. O Gcc começou a introduzir o C ++ 11 (ainda C ++ 0x na época) no 4.3. Esta tabela diz que já possui referências de rvalue e alguns outros recursos menos usados ​​(você deve especificar a -std=c++0xopção para habilitá-los).
  2. Muitas adições à biblioteca padrão no C ++ 11 já foram definidas no TR1 e o GNU stdlibc ++ as fornece no espaço de nome std :: tr1. Portanto, basta fazer o uso condicional apropriado.
  3. O Boost define a maioria das funções TR1 e pode injetá-las no espaço de nomes TR1 se você não as tiver (mas o VS2010 o faz e o gcc 4.3 também o fazem se você usar o GNU stdlibc ++).
  4. Colocar qualquer coisa no stdespaço para nome é um "comportamento indefinido". Isso significa que a especificação não diz o que vai acontecer. Mas se você souber que em uma plataforma específica a biblioteca padrão não define algo, basta seguir em frente e defini-lo. Apenas espere que você tenha que verificar em cada plataforma o que precisa e o que pode definir.
  5. A proposta de referências de valores-limite, N1690, menciona como implementar a semântica de movimentação no C ++ 03. Isso poderia ser usado para substituir unique_ptr. No entanto, isso não seria muito útil, porque depende de coleções realmente usando a semântica de movimentação e as do C ++ 03 obviamente não.
Jan Hudec
fonte
1
Você está correto sobre o GCC, mas infelizmente também tenho que dar suporte a outros compiladores (que não são do GCC). Sua bala nº 4 está no centro da pergunta que estou fazendo. O número 5 é interessante, mas não pretendo oferecer suporte à semântica de movimentação (a otimização de cópia) nessas plataformas mais antigas, mas apenas "std :: move ()" como uma sintaxe compilável.
Mcmcc