Scott Meyers aborda esse assunto muito bem e detalhadamente. Seu item nº 2 em "Terceira edição eficaz do C ++". Dois casos especiais (1) const estático é preferido dentro de um escopo de classe para constantes específicas de classe; (2) namespace ou const de escopo anônimo é preferível a #define.
Eric
2
Eu prefiro Enums. Porque é híbrido de ambos. Não ocupa espaço, a menos que você crie uma variável. Se você deseja apenas usar como constante, a enumeração é a melhor opção. Possui segurança de tipo em C / C ++ 11 std e também uma constante perfeita. #define é do tipo inseguro, const ocupa espaço se o compilador não puder otimizá-lo.
siddhusingh
1
Minha decisão de usar #defineou static const(para seqüências de caracteres) é orientada pelo aspecto de inicialização (não foi mencionado pelas respostas abaixo): se a constante for usada apenas em uma unidade de compilação específica, eu continuarei com static const, caso contrário, uso #define- evite o fiasco de inicialização de ordem estática isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak
Se const, constexprou enumqualquer variação funcionar no seu caso, prefira#define
#
@MartinDvorak " evite o fiasco de inicialização estática da ordem " Como isso é um problema para constantes?
precisa
Respostas:
139
Pessoalmente, eu detesto o pré-processador, então sempre o acompanho const.
A principal vantagem de a #defineé que ela não requer memória para armazenar em seu programa, pois está apenas substituindo algum texto por um valor literal. Ele também tem a vantagem de não ter um tipo, portanto pode ser usado para qualquer valor inteiro sem gerar avisos.
As vantagens de " const" s são que eles podem ter escopo definido e podem ser usados em situações em que um ponteiro para um objeto precisa ser passado.
Não sei exatamente o que você está fazendo com a staticparte " ". Se você estiver declarando globalmente, eu o colocaria em um espaço para nome anônimo em vez de usar static. Por exemplo
namespace{unsignedconst seconds_per_minute =60;};int main (int argc;char*argv[]){...}
As constantes de string são especificamente aquelas que podem se beneficiar de ser #defined, pelo menos se elas puderem ser usadas como "blocos de construção" para constantes de string maiores. Veja minha resposta para um exemplo.
AnT
62
A #definevantagem de não usar nenhuma memória é imprecisa. O "60" no exemplo deve ser armazenado em algum lugar, independentemente de ser static constou #define. De fato, vi compiladores em que o uso de #define causava um consumo maciço de memória (somente leitura) e a const estática não utilizava memória desnecessária.
Gilad Naor
3
Um #define é como se você o tivesse digitado, então definitivamente não vem da memória.
o reverendo
27
@theReverend Os valores literais estão de alguma forma isentos de consumir recursos da máquina? Não, eles podem usá-los de maneiras diferentes, talvez não apareça na pilha ou pilha, mas em algum momento o programa é carregado na memória junto com todos os valores compilados nele.
Sqeaky
13
@ gilad-naor, Você está certo em geral, mas números inteiros pequenos como 60 podem às vezes ser uma espécie de exceção parcial. Alguns conjuntos de instruções têm a capacidade de codificar números inteiros ou um subconjunto de números inteiros diretamente no fluxo de instruções. Por exemplo, os MIPs incluem imediato ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). Nesse tipo de caso, pode-se dizer que um número inteiro #defined realmente não usa espaço, uma vez que, no binário compilado, ele ocupa alguns bits extras nas instruções que, de qualquer maneira, deveriam existir.
ahcox
242
Prós e contras entre #defines, se const(o que você esqueceu) enums, dependendo do uso:
enums:
possível apenas para valores inteiros
problemas de conflito de escopo / identificador adequadamente tratados adequadamente, principalmente nas classes de enumeração C ++ 11 em que as enumerações para enum class Xsão desambiguadas pelo escopoX::
fortemente digitado, mas com um tamanho int com ou sem sinal grande o suficiente sobre o qual você não tem controle no C ++ 03 (embora você possa especificar um campo de bits no qual eles devem ser compactados se o enum for membro de struct / classe / união), enquanto o C ++ 11 é padronizado como intmas pode ser explicitamente definido pelo programador
não pode usar o endereço - não há um, pois os valores de enumeração são efetivamente substituídos em linha nos pontos de uso
restrições de uso mais fortes (por exemplo, incrementar - template <typename T> void f(T t) { cout << ++t; }não será compilado, embora você possa agrupar uma enum em uma classe com construtor implícito, operador de conversão e operadores definidos pelo usuário)
o tipo de cada constante é retirado da enumeração envolvente; portanto, template <typename T> void f(T)obtenha uma instanciação distinta ao passar o mesmo valor numérico de enumerações diferentes, todas distintas de qualquer f(int)instanciação real . O código de objeto de cada função pode ser idêntico (ignorando as compensações de endereço), mas eu não esperaria que um compilador / vinculador eliminasse as cópias desnecessárias, embora você possa verificar seu compilador / vinculador se quiser.
mesmo com typeof / decltype, não pode esperar que numeric_limits forneça informações úteis sobre o conjunto de valores e combinações significativos (na verdade, combinações "legais" nem sequer são notadas no código-fonte, considere enum { A = 1, B = 2 }- é A|B"legal" a partir da lógica de um programa perspectiva?)
o nome do tipo de enum pode aparecer em vários locais no RTTI, nas mensagens do compilador etc. - possivelmente útil, possivelmente ofuscação
você não pode usar uma enumeração sem que a unidade de tradução realmente veja o valor, o que significa que as enumerações nas APIs da biblioteca precisam dos valores expostos no cabeçalho makee outras ferramentas de recompilação baseadas em timestamp acionam a recompilação do cliente quando elas são alteradas (incorreto! )
consts:
problemas de conflito de escopo / identificador adequadamente tratados de maneira adequada
tipo forte, único e especificado pelo usuário
você pode tentar "digitar" uma #defineala #define S std::string("abc"), mas a constante evita a construção repetida de temporários distintos em cada ponto de uso
Complicações de uma regra de definição
pode tomar endereço, criar referências const para eles etc.
mais semelhante a um não constvalor, que minimiza o trabalho e o impacto se alternar entre os dois
O valor pode ser colocado dentro do arquivo de implementação, permitindo que uma recompilação localizada e apenas links de clientes detectem a alteração
#defines:
escopo "global" / mais propenso a usos conflitantes, que podem produzir problemas de compilação difíceis de resolver e resultados inesperados em tempo de execução, em vez de mensagens de erro sãs; mitigar isso requer:
identificadores longos, obscuros e / ou coordenados centralmente, e o acesso a eles não pode se beneficiar da correspondência implícita de namespace usado / atual / procurado por Koenig, alias de namespace etc.
enquanto a melhor prática de trump permite que os identificadores de parâmetro de modelo sejam letras maiúsculas de um caractere (possivelmente seguidos de um número), outro uso de identificadores sem letras minúsculas é convencionalmente reservado e esperado para o pré-processador definido (fora da biblioteca do OS e C / C ++ cabeçalhos). Isso é importante para que o uso do pré-processador em escala corporativa permaneça gerenciável. Pode-se esperar que as bibliotecas de terceiros estejam em conformidade. Observar isso implica a migração de consts ou enumerações existentes para / de define envolve uma mudança na capitalização e, portanto, requer edições no código-fonte do cliente, em vez de uma recompilação "simples". (Pessoalmente, uso em maiúscula a primeira letra de enumerações, mas não consts, para que eu também seja migrado entre as duas - talvez seja hora de repensar isso.)
possíveis operações em tempo de compilação: concatenação literal de string, stringification (tomando o tamanho da mesma), concatenação em identificadores
desvantagem é que dada #define X "x"e alguns ala uso do cliente "pre" X "post", se você quer ou necessidade de fazer X uma variável de tempo de execução-mutável, em vez de uma constante você forçar edições para o código do cliente (em vez de apenas recompilação), enquanto que a transição é mais fácil a partir de um const char*ou const std::stringdado que já força o usuário a incorporar operações de concatenação (por exemplo, "pre" + X + "post"para string)
não pode usar sizeofdiretamente em um literal numérico definido
sem tipo (o GCC não avisa se comparado a unsigned)
algumas cadeias de compilador / vinculador / depurador podem não apresentar o identificador, então você será reduzido a olhar para "números mágicos" (cadeias de caracteres, qualquer que seja ...)
não pode pegar o endereço
o valor substituído não precisa ser legal (ou discreto) no contexto em que o #define é criado, pois é avaliado em cada ponto de uso, para que você possa fazer referência a objetos ainda não declarados, dependendo da "implementação" que não precisa pré-incluído, crie "constantes" como as { 1, 2 }que podem ser usadas para inicializar matrizes ou #define MICROSECONDS *1E-6etc. ( definitivamente não é recomendável!)
algumas coisas especiais, como __FILE__e __LINE__podem ser incorporadas à substituição macro
você pode testar a existência e o valor nas #ifinstruções para incluir o código condicionalmente (mais poderoso que um pós-pré-processamento "se", pois o código não precisa ser compilável se não for selecionado pelo pré-processador), usar #undef-ine, redefinir etc.
o texto substituído deve ser exposto:
na unidade de tradução usada, o que significa que as macros nas bibliotecas para uso do cliente devem estar no cabeçalho; portanto, makeoutras ferramentas de recompilação baseadas em timestamp acionam a recompilação do cliente quando elas são alteradas (incorreto!)
ou na linha de comando, onde é necessário ainda mais cuidado para garantir que o código do cliente seja recompilado (por exemplo, o Makefile ou o script que fornece a definição deve ser listado como uma dependência)
Minha opinião pessoal:
Como regra geral, eu uso consts e os considero a opção mais profissional para uso geral (embora os outros tenham uma simplicidade atraente para esse programador preguiçoso).
Resposta incrível. Um pequeno detalhe: algumas vezes eu uso enumerações locais que não estão nos cabeçalhos, apenas para maior clareza do código, como em máquinas de estado pequenas e coisas do tipo. Portanto, eles não precisam estar em cabeçalhos o tempo todo.
kert
Os prós e os contras estão misturados, eu gostaria muito de ver uma tabela de comparação.
usar o seguinte código
@ Unknown123: fique à vontade para postar um - não me importo se você arrancar algum ponto que se sinta digno daqui. Cheers
Tony Delroy
48
Se essa é uma pergunta do C ++ e menciona #definecomo alternativa, trata-se de constantes "globais" (ou seja, no escopo do arquivo), não de membros da classe. Quando se trata de tais constantes no C ++, static consté redundante. No C ++, consthá vínculo interno por padrão e não faz sentido declará-los static. Por isso, é realmente sobre constvs. #define.
E, finalmente, em C ++ consté preferível. Pelo menos porque essas constantes são digitadas e com escopo definido. Simplesmente não existem razões para preferir #definemais const, além de algumas exceções.
As constantes de string, BTW, são um exemplo dessa exceção. Com #defineconstantes de cadeia d, pode-se usar o recurso de concatenação em tempo de compilação dos compiladores C / C ++, como em
PS Novamente, quando alguém menciona static constcomo alternativa #define, geralmente significa que eles estão falando sobre C, não sobre C ++. Gostaria de saber se esta pergunta está marcada corretamente ...
" simplesmente não há razões para preferir #define " Sobre o que? Variáveis estáticas definidas em um arquivo de cabeçalho?
precisa
9
#define pode levar a resultados inesperados:
#include<iostream>#define x 500#define y x +5int z = y *2;int main(){
std::cout <<"y is "<< y;
std::cout <<"\nz is "<< z;}
Emite um resultado incorreto:
y is505
z is510
No entanto, se você substituir isso por constantes:
#include<iostream>constint x =500;constint y = x +5;int z = y *2;int main(){
std::cout <<"y is "<< y;
std::cout <<"\nz is "<< z;}
Ele gera o resultado correto:
y is505
z is1010
Isso ocorre porque #definesimplesmente substitui o texto. Como isso pode atrapalhar seriamente a ordem das operações, eu recomendaria o uso de uma variável constante.
Eu tinha um resultado inesperado diferente: ytinha o valor 5500, um pouco-endian concatenação de xe 5.
Códigos com martelo
5
Usar uma const estática é como usar outras variáveis const no seu código. Isso significa que você pode rastrear de onde vêm as informações, em vez de um #define que será simplesmente substituído no código no processo de pré-compilação.
Uma const estática é digitada (tem um tipo) e pode ser verificada pelo compilador quanto à validade, redefinição etc.
um #define pode ser redefinido indefinidamente.
Geralmente você deve preferir consts estáticos. Não tem desvantagem. O pré-processador deve ser usado principalmente para compilação condicional (e algumas vezes para trics realmente sujos, talvez).
Definir constantes usando a diretiva pré-processador #definenão é recomendado para aplicar não apenas em C++, mas também em C. Essas constantes não terão o tipo Mesmo em Cfoi proposto para usar constpara constantes.
Sempre prefira usar os recursos de idioma em vez de algumas ferramentas adicionais, como o pré-processador.
ES.31: Não use macros para constantes ou "funções"
As macros são uma das principais fontes de erros. As macros não obedecem às regras usuais de escopo e tipo. As macros não obedecem às regras usuais para a passagem de argumentos. As macros garantem que o leitor humano veja algo diferente do que o compilador vê. Macros complicam a construção de ferramentas.
Se você estiver definindo uma constante a ser compartilhada entre todas as instâncias da classe, use const estático. Se a constante for específica para cada instância, basta usar const (mas observe que todos os construtores da classe devem inicializar essa variável de membro const na lista de inicialização).
#define
oustatic const
(para seqüências de caracteres) é orientada pelo aspecto de inicialização (não foi mencionado pelas respostas abaixo): se a constante for usada apenas em uma unidade de compilação específica, eu continuarei comstatic const
, caso contrário, uso#define
- evite o fiasco de inicialização de ordem estática isocpp.org/wiki/faq/ctors#static-init-orderconst
,constexpr
ouenum
qualquer variação funcionar no seu caso, prefira#define
Respostas:
Pessoalmente, eu detesto o pré-processador, então sempre o acompanho
const
.A principal vantagem de a
#define
é que ela não requer memória para armazenar em seu programa, pois está apenas substituindo algum texto por um valor literal. Ele também tem a vantagem de não ter um tipo, portanto pode ser usado para qualquer valor inteiro sem gerar avisos.As vantagens de "
const
" s são que eles podem ter escopo definido e podem ser usados em situações em que um ponteiro para um objeto precisa ser passado.Não sei exatamente o que você está fazendo com a
static
parte " ". Se você estiver declarando globalmente, eu o colocaria em um espaço para nome anônimo em vez de usarstatic
. Por exemplofonte
#define
d, pelo menos se elas puderem ser usadas como "blocos de construção" para constantes de string maiores. Veja minha resposta para um exemplo.#define
vantagem de não usar nenhuma memória é imprecisa. O "60" no exemplo deve ser armazenado em algum lugar, independentemente de serstatic const
ou#define
. De fato, vi compiladores em que o uso de #define causava um consumo maciço de memória (somente leitura) e a const estática não utilizava memória desnecessária.Prós e contras entre
#define
s, seconst
(o que você esqueceu)enum
s, dependendo do uso:enum
s:enum class X
são desambiguadas pelo escopoX::
int
mas pode ser explicitamente definido pelo programadortemplate <typename T> void f(T t) { cout << ++t; }
não será compilado, embora você possa agrupar uma enum em uma classe com construtor implícito, operador de conversão e operadores definidos pelo usuário)template <typename T> void f(T)
obtenha uma instanciação distinta ao passar o mesmo valor numérico de enumerações diferentes, todas distintas de qualquerf(int)
instanciação real . O código de objeto de cada função pode ser idêntico (ignorando as compensações de endereço), mas eu não esperaria que um compilador / vinculador eliminasse as cópias desnecessárias, embora você possa verificar seu compilador / vinculador se quiser.enum { A = 1, B = 2 }
- éA|B
"legal" a partir da lógica de um programa perspectiva?)make
e outras ferramentas de recompilação baseadas em timestamp acionam a recompilação do cliente quando elas são alteradas (incorreto! )const
s:#define
ala#define S std::string("abc")
, mas a constante evita a construção repetida de temporários distintos em cada ponto de usoconst
valor, que minimiza o trabalho e o impacto se alternar entre os dois#define
s:#define X "x"
e alguns ala uso do cliente"pre" X "post"
, se você quer ou necessidade de fazer X uma variável de tempo de execução-mutável, em vez de uma constante você forçar edições para o código do cliente (em vez de apenas recompilação), enquanto que a transição é mais fácil a partir de umconst char*
ouconst std::string
dado que já força o usuário a incorporar operações de concatenação (por exemplo,"pre" + X + "post"
parastring
)sizeof
diretamente em um literal numérico definidounsigned
){ 1, 2 }
que podem ser usadas para inicializar matrizes ou#define MICROSECONDS *1E-6
etc. ( definitivamente não é recomendável!)__FILE__
e__LINE__
podem ser incorporadas à substituição macro#if
instruções para incluir o código condicionalmente (mais poderoso que um pós-pré-processamento "se", pois o código não precisa ser compilável se não for selecionado pelo pré-processador), usar#undef
-ine, redefinir etc.make
outras ferramentas de recompilação baseadas em timestamp acionam a recompilação do cliente quando elas são alteradas (incorreto!)Minha opinião pessoal:
Como regra geral, eu uso
const
s e os considero a opção mais profissional para uso geral (embora os outros tenham uma simplicidade atraente para esse programador preguiçoso).fonte
Se essa é uma pergunta do C ++ e menciona
#define
como alternativa, trata-se de constantes "globais" (ou seja, no escopo do arquivo), não de membros da classe. Quando se trata de tais constantes no C ++,static const
é redundante. No C ++,const
há vínculo interno por padrão e não faz sentido declará-losstatic
. Por isso, é realmente sobreconst
vs.#define
.E, finalmente, em C ++
const
é preferível. Pelo menos porque essas constantes são digitadas e com escopo definido. Simplesmente não existem razões para preferir#define
maisconst
, além de algumas exceções.As constantes de string, BTW, são um exemplo dessa exceção. Com
#define
constantes de cadeia d, pode-se usar o recurso de concatenação em tempo de compilação dos compiladores C / C ++, como emPS Novamente, quando alguém menciona
static const
como alternativa#define
, geralmente significa que eles estão falando sobre C, não sobre C ++. Gostaria de saber se esta pergunta está marcada corretamente ...fonte
#define
pode levar a resultados inesperados:Emite um resultado incorreto:
No entanto, se você substituir isso por constantes:
Ele gera o resultado correto:
Isso ocorre porque
#define
simplesmente substitui o texto. Como isso pode atrapalhar seriamente a ordem das operações, eu recomendaria o uso de uma variável constante.fonte
y
tinha o valor5500
, um pouco-endian concatenação dex
e 5.Usar uma const estática é como usar outras variáveis const no seu código. Isso significa que você pode rastrear de onde vêm as informações, em vez de um #define que será simplesmente substituído no código no processo de pré-compilação.
Você pode dar uma olhada no FAQ do C ++ Lite para esta pergunta: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7
fonte
Geralmente você deve preferir consts estáticos. Não tem desvantagem. O pré-processador deve ser usado principalmente para compilação condicional (e algumas vezes para trics realmente sujos, talvez).
fonte
Definir constantes usando a diretiva pré-processador
#define
não é recomendado para aplicar não apenas emC++
, mas também emC
. Essas constantes não terão o tipo Mesmo emC
foi proposto para usarconst
para constantes.fonte
Por favor, veja aqui: static const vs define
geralmente uma declaração const (observe que não precisa ser estática) é o caminho a percorrer
fonte
Sempre prefira usar os recursos de idioma em vez de algumas ferramentas adicionais, como o pré-processador.
Das diretrizes principais do C ++
fonte
Se você estiver definindo uma constante a ser compartilhada entre todas as instâncias da classe, use const estático. Se a constante for específica para cada instância, basta usar const (mas observe que todos os construtores da classe devem inicializar essa variável de membro const na lista de inicialização).
fonte