static const vs #define


212

É melhor usar static constvars do que #definepré-processador? Ou talvez dependa do contexto?

Quais são as vantagens / desvantagens de cada método?


14
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 {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
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:

  1. 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! )

  1. 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

  1. #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).


1
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

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

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


1
" 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 + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Emite um resultado incorreto:

y is 505
z is 510

No entanto, se você substituir isso por constantes:

#include <iostream>

const int x = 500;
const int 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 is 505
z is 1010

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.


1
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.

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


4
  • 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).


3

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.



2

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.

Das diretrizes principais do C ++


0

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).

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.