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?

Patrice Bernassola
fonte
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[]) {
...
}
TED
fonte
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).

Tony Delroy
fonte
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 ...

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

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

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

RED SOFT ADAIR
fonte
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.


fonte
2

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

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

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

snr
fonte