O #pragma já foi seguro incluir guarda?


311

Eu li que existe alguma otimização do compilador ao usar, o #pragma onceque pode resultar em uma compilação mais rápida. Reconheço que isso não é padrão e, portanto, pode representar um problema de compatibilidade entre plataformas.

Isso é suportado pelos compiladores mais modernos em plataformas não-windows (gcc)?

Eu quero evitar problemas de compilação de plataforma, mas também quero evitar o trabalho extra dos guardas de fallback:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Devo me preocupar? Devo gastar mais energia mental nisso?


3
Depois de fazer uma pergunta semelhante , descobri que #pragma onceparece evitar alguns problemas de exibição de classe no VS 2008. Estou no processo de livrar-me dos protetores de inclusão e substituí-los #pragma oncepor esse motivo.
SmacL

Respostas:


189

O uso #pragma oncedeve funcionar em qualquer compilador moderno, mas não vejo motivo para não usar um #ifndefguarda de inclusão padrão . Funciona muito bem. A única ressalva é que o GCC não suportava #pragma onceantes da versão 3.4 .

Também descobri que, pelo menos no GCC, ele reconhece o padrão #ifndefinclui guarda e o otimiza , portanto, não deve ser muito mais lento que #pragma once.


12
Não deve ser mais lento (com o GCC de qualquer maneira).
21139 Jason Coco

54
Não é implementado dessa maneira. Em vez disso, se o arquivo iniciar com um #ifndef pela primeira vez e terminar com um #endif, o gcc se lembrará dele e sempre ignorará os que incluirem no futuro, sem se preocupar em abrir o arquivo.
Jason Coco

10
#pragma oncegeralmente é mais rápido porque o arquivo não está sendo pré-processado. ifndef/define/endifrequer pré-processamento de qualquer maneira, porque após este bloco pode ter compilable algo (teoricamente)
Andrey

13
Documentos do GCC sobre a otimização da macro de guarda: gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
Adrian

38
Para usar as proteções de inclusão, existe o requisito extra de que você deve definir um novo símbolo, como #ifndef FOO_BAR_Hnormalmente para um arquivo como "foo_bar.h". Se você renomear este arquivo posteriormente, deve ajustar as proteções de inclusão para que sejam consistentes com esta convenção? Além disso, se você tiver dois foo_bar.h distintos em dois lugares diferentes na sua árvore de códigos, deverá pensar em dois símbolos diferentes para cada um. A resposta curta é usar #pragma oncee, se você realmente precisar compilar em um ambiente que não o suporte, vá em frente e adicione protetores de inclusão para esse ambiente.
Brandin

329

#pragma once tem uma desvantagem (além de não ser padrão) e, se você tiver o mesmo arquivo em locais diferentes (temos isso porque nosso sistema de criação copia arquivos), o compilador pensará que esses são arquivos diferentes.


36
Mas você também pode ter dois arquivos com o mesmo nome em locais diferentes, sem ter que se preocupar em criar NAMES #define diferentes, que são muitas vezes na forma de HEADERFILENAME_H
Vargas

69
Você também pode ter dois ou mais arquivos com o mesmo #define WHATEVER, o que não causa diversão nenhuma, e é por isso que eu preferiria usar o pragma uma vez.
Chris Huang-Leaver

107
Não persuasivo ... Altere o sistema de compilação para um que não copie arquivos, mas use links simbólicos ou inclua o mesmo arquivo apenas de um local em cada unidade de tradução. Parece mais que sua infraestrutura é uma bagunça que precisa ser reorganizada.
Yakov Galka

3
E se você tiver arquivos diferentes com o mesmo nome em diretórios diferentes, a abordagem #ifdef pensará que eles são o mesmo arquivo. Portanto, há uma desvantagem para um, e há uma desvantagem para o outro.
Rxantos #

3
@rxantos, se os arquivos diferem, o #ifdefvalor da macro também pode ser diferente.
Motti

63

Eu gostaria que #pragma once(ou algo parecido) estivesse no padrão. A inclusão de guardas não é um grande problema (mas parece um pouco difícil de explicar para as pessoas que aprendem o idioma), mas parece um pequeno aborrecimento que poderia ter sido evitado.

De fato, desde 99,98% do tempo, o #pragma oncecomportamento é o comportamento desejado, seria bom se a prevenção da inclusão múltipla de um cabeçalho fosse tratada automaticamente pelo compilador, com um #pragmaou algo para permitir a inclusão dupla.

Mas nós temos o que temos (exceto que você pode não ter #pragma once).


48
O que eu realmente quero é uma #importdiretiva padrão .
John

10
Uma diretiva de importação padrão está chegando: isocpp.org/blog/2012/11/… Mas ainda não está aqui. Eu sou fortemente a favor disso.
AHelps

6
@AHelps Vaporware. Já faz quase cinco anos agora. Talvez em 2023 você volte a esse comentário e diga "eu te disse".
doug65536

Não é um vaporware, mas apenas no estágio de Especificação Técnica. Os módulos são implementados no Visual Studio 2015 ( blogs.msdn.microsoft.com/vcblog/2015/12/03/… ) e no clang ( clang.llvm.org/docs/Modules.html ). E é importação, não # importação.
AHelps

Deve torná-lo em C ++ 20.
Ionoclast Brigham

36

Não conheço nenhum benefício de desempenho, mas certamente funciona. Eu o uso em todos os meus projetos C ++ (desde que eu esteja usando o compilador MS). Acho que é mais eficaz do que usar

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

Ele faz o mesmo trabalho e não preenche o pré-processador com macros adicionais.

O GCC suporta #pragma onceoficialmente a partir da versão 3.4 .


25

O GCC suporta #pragma oncedesde 3.4, consulte http://en.wikipedia.org/wiki/Pragma_once para obter mais suporte do compilador.

A grande vantagem que vejo ao usar #pragma once em vez de incluir guardas, é evitar erros de copiar / colar.

Vamos ser sinceros: a maioria de nós dificilmente inicia um novo arquivo de cabeçalho do zero, mas apenas copia um existente e modifica-o de acordo com nossas necessidades. É muito mais fácil criar um modelo de trabalho usando#pragma once vez de incluir guardas. Quanto menos eu preciso modificar o modelo, menor a probabilidade de ocorrer erros. Ter a mesma proteção de inclusão em arquivos diferentes leva a erros estranhos do compilador e leva algum tempo para descobrir o que deu errado.

TL; DR: #pragma onceé mais fácil de usar.


11

Eu o uso e estou feliz com isso, pois preciso digitar muito menos para criar um novo cabeçalho. Funcionou bem para mim em três plataformas: Windows, Mac e Linux.

Não tenho nenhuma informação de desempenho, mas acredito que a diferença entre #pragma e a proteção de inclusão não será nada comparada à lentidão de analisar a gramática C ++. Esse é o verdadeiro problema. Tente compilar o mesmo número de arquivos e linhas com um compilador C #, por exemplo, para ver a diferença.

No final, usar a guarda ou o pragma, não importará nada.


Não gosto do #pragma uma vez, mas agradeço que você aponte os benefícios relativos ... A análise de C ++ é muito mais cara do que qualquer outra coisa, em um ambiente operacional "normal". Ninguém compila a partir de um sistema de arquivos remoto se o tempo de compilação for um problema.
Tom

1
Re C ++ analisando lentidão vs. C #. Em C #, você não precisa analisar (literalmente) milhares de LOC de arquivos de cabeçalho (iostream, alguém?) Para cada pequeno arquivo C ++. Use cabeçalhos pré-compilados para fazer este problema menor, no entanto
Eli Bendersky

11

O uso de ' #pragma once' pode não ter nenhum efeito (não é suportado em todos os lugares - embora seja cada vez mais suportado), então você precisa usar o código de compilação condicional de qualquer maneira; nesse caso, por que se preocupar com ' #pragma once'? O compilador provavelmente o otimiza de qualquer maneira. Depende das plataformas de destino, no entanto. Se todos os seus objetivos o suportarem, vá em frente e use-o - mas deve ser uma decisão consciente, porque todo o inferno se abrirá se você usar apenas o pragma e depois portar para um compilador que não o suporte.


1
Eu discordo que você precisa apoiar os guardas de qualquer maneira. Se você usar o #pragma uma vez (ou guardas), isso ocorre porque gera alguns conflitos sem eles. Portanto, se ele não for suportado por sua ferramenta de cadeia, o projeto simplesmente não será compilado e você estará exatamente no mesmo tipo de situação que quando deseja compilar algum ansi C em um antigo compilador K&R. Você só precisa obter um chaintool mais atualizado ou alterar o código para adicionar alguns guardas. Todo o inferno seria se o programa estivesse sendo compilado, mas não funcionasse.
kriss

5

O benefício de desempenho é não ter que reabrir o arquivo depois que o #pragma tiver sido lido. Com os guardas, o compilador precisa abrir o arquivo (que pode ser caro no tempo) para obter as informações que não devem incluir seu conteúdo novamente.

Isso é teoria apenas porque alguns compiladores não abrirão automaticamente arquivos que não continham nenhum código de leitura para cada unidade de compilação.

De qualquer forma, não é o caso de todos os compiladores, então, idealmente, o #pragma deve ser evitado para o código de plataforma cruzada, se não for padrão de todo / não tiver definição e efeito padronizados. No entanto, na prática, é realmente melhor que guardas.

No final, a melhor sugestão que você pode ter para obter a melhor velocidade do seu compilador, sem precisar verificar o comportamento de cada compilador nesse caso, é usar o pragma uma vez e os guardas.

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

Dessa forma, você obtém o melhor de ambos (multiplataforma e ajuda na velocidade de compilação).

Como é mais longo para digitar, eu pessoalmente uso uma ferramenta para ajudar a gerar tudo isso de uma maneira muito complicada (Visual Assist X).


O Visual Studio não otimiza #include guardas como estão? Outro (melhor?) Compilador faz isso, e imagino que seja bastante fácil.
Tom

1
Por que você coloca o pragmadepois do ifndef? Existe algum benefício?
User1095108

1
@ user1095108 Alguns compiladores usarão os protetores de cabeçalho como delimitador para saber se o arquivo contém apenas código que precisa ser instanciado uma vez. Se algum código estiver fora dos protetores de cabeçalho, o arquivo inteiro poderá ser considerado talvez instável mais de uma vez. Se o mesmo compilador não suportar o pragma uma vez, ele ignorará essa instrução. Portanto, colocar o pragma uma vez dentro dos protetores de cabeçalho é a maneira mais genérica de garantir que pelo menos os protetores de cabeçalho possam ser "otimizados".
Klaim

4

Nem sempre.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 possui um bom exemplo de dois arquivos destinados a serem incluídos, mas pensados ​​erroneamente como idênticos devido a carimbos de data e hora e conteúdo idênticos (nome de arquivo não idêntico) .


10
Isso seria um bug no compilador. (tentar usar um atalho que não deve levar).
rxantos

4
#pragma oncenão é padrão, portanto, o que um compilador decide fazer é "correto". Claro, então podemos começar a falar sobre o que é "esperado" e o que é "útil".
precisa saber é o seguinte

2

Usando o gcc 3.4 e 4.1 em árvores muito grandes (às vezes usando o distcc ), ainda não vi nenhuma velocidade ao usar o #pragma uma vez, ou em combinação com os protetores de inclusão padrão.

Realmente não vejo como vale a pena confundir versões antigas do gcc, ou mesmo outros compiladores, já que não há economia real. Eu não tentei todos os vários delineadores, mas estou disposto a apostar que isso irá confundir muitos deles.

Eu também gostaria que tivesse sido adotado desde o início, mas posso ver o argumento "Por que precisamos disso quando ifndef funciona perfeitamente bem?". Dados os muitos cantos escuros e complexidades de C, incluir guardas é uma das coisas mais fáceis de se explicar. Se você tem um pequeno conhecimento de como o pré-processador funciona, eles devem ser auto-explicativos.

Se você observar uma aceleração significativa, no entanto, atualize sua pergunta.


2

Hoje, os guardas da velha guarda são tão rápidos quanto um #pragma uma vez. Mesmo que o compilador não os trate especialmente, ele ainda será interrompido quando vir #ifndef WHATEVER e WHATEVER for definido. Abrir um arquivo é muito barato hoje. Mesmo que houvesse uma melhoria, seria da ordem de milissegundos.

Simplesmente não uso o #pragma uma vez, pois não tem nenhum benefício. Para evitar conflitos com outros guardas de inclusão, uso algo como: CI_APP_MODULE_FILE_H -> CI = Iniciais da empresa; APP = nome do aplicativo; o resto é auto-explicativo.


19
Não é o benefício que é muito menos digitação?
Andrey

1
Observe que alguns milissegundos cem mil vezes são alguns minutos. Os grandes projetos consistem em dez milhares de arquivos, incluindo dezenas de cabeçalhos cada. Dadas as CPUs de muitos núcleos atuais, a entrada / saída, em particular a abertura de muitos arquivos pequenos , é um dos principais gargalos.
Damon

1
"Hoje, os guardas da velha guarda são tão rápidos quanto um #pragma uma vez." Hoje, e também há muitos anos. Os documentos mais antigos do site do GCC são para 2,95 a partir de 2001 e a otimização da inclusão de guardas não era nova na época. Não é uma otimização recente.
Jonathan Wakely

4
O principal benefício é que os guardas são propensos a erros e prolixo. É muito fácil ter dois arquivos diferentes com nomes idênticos em diretórios diferentes (e os protetores de inclusão podem ser o mesmo símbolo) ou cometer erros de copiar e colar durante a cópia dos protetores de inclusão. Pragma é menos propenso a erros e funciona em todas as principais plataformas de PC. Se você pode usá-lo, é melhor estilo.
AHelps

2

A principal diferença é que o compilador precisou abrir o arquivo de cabeçalho para ler a proteção de inclusão. Em comparação, o pragma faz com que o compilador acompanhe o arquivo e não faça E / S de arquivo quando se depara com outra inclusão para o mesmo arquivo. Embora isso possa parecer insignificante, ele pode ser facilmente escalado com grandes projetos, especialmente aqueles sem um bom cabeçalho incluem disciplinas.

Dito isto, hoje em dia os compiladores (incluindo o GCC) são inteligentes o suficiente para tratar de incluir guardas como o pragma uma vez. ou seja, eles não abrem o arquivo e evitam a penalidade de IO do arquivo.

Nos compiladores que não suportam pragma, vi implementações manuais um pouco complicadas.

#ifdef FOO_H
#include "foo.h"
#endif

Pessoalmente, gosto da abordagem #pragma once, pois evita o incômodo de colisões de nomes e possíveis erros de digitação. Também é um código mais elegante em comparação. Dito isto, para código portátil, não deve doer ter os dois, a menos que o compilador se queixe.


1
"Dito isto, hoje em dia os compiladores (incluindo o GCC) são inteligentes o suficiente para tratar de incluir guardas como o pragma uma vez". Eles fazem isso há décadas, talvez mais do que #pragma onceexistia!
Jonathan Wakely

Pense que você me entendeu mal. Eu quis dizer que antes do pragma uma vez, todos os compiladores incorriam em várias penantes de E / S para o mesmo arquivo h incluído várias vezes durante o estágio de pré-processador. As implementações modernas acabam usando um melhor cache de arquivos no estágio de pré-processador. Independentemente, sem pragmas, o estágio do pré-processador ainda inclui tudo o que está fora dos protetores de inclusão. Com o pragma uma vez, todo o arquivo é deixado de fora. Desse ponto de vista, o pragma ainda é vantajoso.
Shammi

1
Não, está errado, compiladores decentes deixam o arquivo inteiro de fora mesmo sem #pragma uma vez, eles não abrem o arquivo uma segunda vez e nem olham para ele uma segunda vez, consulte gcc.gnu.org/onlinedocs/ cpp / Once-Only-Headers.html (isso não tem nada a ver com cache).
Jonathan Wakely

1
No seu link, parece que a otimização ocorre apenas no cpp. Independentemente disso, o cache entra em jogo. Como o pré-processador sabe incluir o código fora dos guardas. Exemplo ... extern int foo; #ifndef INC_GUARD #define a classe INC_GUARD ClassInHeader {}; #endif Nesse caso, o pré-processador precisará incluir extern int foo; várias vezes se você incluir o mesmo arquivo várias vezes. Final do dia, não há muito ponto discutindo sobre isso enquanto nós entendemos a diferença entre #pragma once e incluem guardas e como vários compiladores se comportar com os dois :)
Shammi

1
Não se aplica a otimização nisso, obviamente.
Jonathan Wakely /

1

Se usarmos o msvc ou o Qt (até o Qt 4.5), desde o GCC (até 3.4), o msvc suporta #pragma once, não vejo razão para não usar #pragma once.

O nome do arquivo de origem geralmente é o mesmo nome de classe igual e sabemos que, às vezes, precisamos refatorar para renomear o nome da classe, e tivemos que mudar #include XXXXtambém, então acho que manter o manual #include xxxxxnão é um trabalho inteligente. mesmo com a extensão do Visual Assist X, manter o "xxxx" não é um trabalho necessário.


1

Nota adicional para as pessoas que pensam que uma inclusão automática única de arquivos de cabeçalho é sempre desejada: eu construo geradores de código usando a inclusão dupla ou múltipla de arquivos de cabeçalho desde décadas. Especialmente para a geração de stubs de bibliotecas de protocolo, acho muito confortável ter um gerador de código extremamente portátil e poderoso, sem ferramentas e idiomas adicionais. Eu não sou o único desenvolvedor que usa esse esquema, pois esse blog X-Macros mostram os . Isso não seria possível sem a proteção automática ausente.


Os modelos C ++ poderiam resolver o problema? Eu raramente encontro qualquer necessidade válida de macros devido à forma como os modelos C ++.
Clearer

1
Nossa experiência profissional de longo prazo é que o uso de infraestrutura madura de software, software e ferramentas sempre oferece a nós, como prestadores de serviços (Sistemas Incorporados), uma enorme vantagem em produtividade e flexibilidade. Os concorrentes que desenvolvem software e pilhas de sistemas embarcados baseados em C ++ podem achar alguns de seus desenvolvedores mais felizes no trabalho. Mas geralmente superamos o tempo de colocação no mercado, a funcionalidade e a flexibilidade várias vezes. O Nether subestima os ganhos de produtividade ao usar uma e a mesma ferramenta repetidamente. Em vez disso, os Web-Devs sofrem com os caminhos para muitos Frameworks.
Marcel

Uma observação: não está incluindo guardas / # pragma uma vez em cada arquivo de cabeçalho contra o próprio princípio DRY. Eu posso ver o seu ponto no X-Macrorecurso, mas não é o principal uso do include, não deveria ser o contrário, como header unguard / # pragma multi, se fiquemos com DRY?
caiohamamura

DRY significa "Não se repita". É sobre o humano. O que a máquina está fazendo não tem nada a ver com esse paradigma. Os modelos C ++ se repetem bastante, os compiladores C também o fazem (por exemplo, desenrolamento de loop) e todo computador está repetindo quase tudo inacreditável com frequência e rapidez.
Marcel
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.