Além do fato de que sua macro é uma inte sua constexpr unsignedé uma unsigned, existem diferenças importantes e as macros têm apenas uma vantagem.
Escopo
Uma macro é definida pelo pré-processador e é simplesmente substituída no código toda vez que ocorre. O pré-processador é burro e não entende a sintaxe ou semântica do C ++. As macros ignoram escopos, como namespaces, classes ou blocos de função, portanto, você não pode usar um nome para qualquer outra coisa em um arquivo de origem. Isso não é verdade para uma constante definida como uma variável C ++ adequada:
É bom ter uma variável de membro chamada max_heightporque é um membro de classe e, portanto, tem um escopo diferente e é diferente daquele no escopo do namespace. Se você tentar reutilizar o nome MAX_HEIGHTdo membro, o pré-processador irá alterá-lo para este absurdo que não compilaria:
classWindow {// ...int720;
};
É por isso que você deve fornecer macros UGLY_SHOUTY_NAMESpara garantir que eles se destaquem e você pode ter cuidado ao nomeá-los para evitar conflitos. Se você não usa macros desnecessariamente, não precisa se preocupar com isso (e não precisa ler SHOUTY_NAMES).
Se você quer apenas uma constante dentro de uma função, você não pode fazer isso com uma macro, porque o pré-processador não sabe o que é uma função ou o que significa estar dentro dela. Para limitar uma macro a apenas uma determinada parte de um arquivo, você precisa #undefdela novamente:
Uma variável constexpr é uma variável, então ela realmente existe no programa e você pode fazer coisas normais em C ++ como pegar seu endereço e vincular uma referência a ele.
O problema é que MAX_HEIGHTnão é uma variável, portanto, para a chamada de std::maxum temporário intdeve ser criada pelo compilador. A referência que é retornada por std::maxpode então referir-se àquele temporário, que não existe após o final dessa instrução, então return hacessa a memória inválida.
Esse problema simplesmente não existe com uma variável adequada, porque tem um local fixo na memória que não desaparece:
(Na prática, você provavelmente int hnão declararia, const int& hmas o problema pode surgir em contextos mais sutis.)
Condições do pré-processador
O único momento para preferir uma macro é quando você precisa que seu valor seja compreendido pelo pré-processador, para uso em #ifcondições, por exemplo
Você não pode usar uma variável aqui, porque o pré-processador não entende como se referir às variáveis pelo nome. Ele só entende coisas básicas muito básicas, como expansão macro e diretivas que começam com #(como #includee #definee #if).
Se você quiser uma constante que possa ser entendida pelo pré - processador , você deve usar o pré-processador para defini-la. Se você quiser uma constante para o código C ++ normal, use o código C ++ normal.
O exemplo acima é apenas para demonstrar uma condição de pré-processador, mas mesmo esse código pode evitar o uso do pré-processador:
using height_type = std::conditional_t<max_height < 256, unsignedchar, unsignedint>;
Uma constexprvariável não precisa ocupar memória até que seu endereço (um ponteiro / referência) seja obtido; caso contrário, pode ser totalmente otimizado (e acho que pode haver o Standardese que garante isso). Quero enfatizar isso para que as pessoas não continuem usando o velho e inferior ' enumhack' de uma ideia equivocada de que um trivial constexprque não requer armazenamento irá ocupar algum.
sublinhado_d
3
Sua seção "A real memory location" está errada: 1. Você está retornando por valor (int), então uma cópia é feita, o temporário não é um problema. 2. Se você tivesse retornado por referência (int &), int heightseria tão problemático quanto a macro, já que seu escopo está vinculado à função, essencialmente temporário. 3. O comentário acima, "const int & h irá estender a vida útil do temporário" está correto.
PoweredByRice
4
@underscore_d true, mas isso não muda o argumento. A variável não exigirá armazenamento a menos que haja um uso odr dela. O ponto é que, quando uma variável real com armazenamento é necessária, a variável constexpr faz a coisa certa.
Jonathan Wakely
1
@PoweredByRice 1. o problema não tem nada a ver com o valor de retorno de limit, o problema é o valor de retorno de std::max. 2. sim, por isso não retorna uma referência. 3. errado, veja o link coliru acima.
Jonathan Wakely
3
@PoweredByRice suspiro, você realmente não precisa explicar como o C ++ funciona para mim. Se você tiver const int& h = max(x, y);e maxretornar pelo valor, a vida útil do valor de retorno será estendida. Não pelo tipo de retorno, mas pelo ao const int&qual está vinculado. O que escrevi está correto.
Jonathan Wakely
11
De modo geral, você deve usar constexprsempre que puder e macros apenas se nenhuma outra solução for possível.
Justificativa:
As macros são uma simples substituição no código e, por esse motivo, costumam gerar conflitos (por exemplo, maxmacro windows.h vs std::max). Além disso, uma macro que funciona pode ser facilmente usada de uma maneira diferente, o que pode desencadear erros de compilação estranhos. (por exemplo, Q_PROPERTYusado em membros da estrutura)
Devido a todas essas incertezas, é um bom estilo de código evitar macros, exatamente como você normalmente evitaria gotos.
constexpr é semanticamente definido e, portanto, normalmente gera muito menos problemas.
Compilação condicional usando, por exemplo, #ifcoisas para as quais o pré-processador é realmente útil. Definir uma constante não é uma das coisas para as quais o pré-processador seja útil, a menos que essa constante deva ser uma macro porque é usada em condições de pré-processador #if. Se a constante for para uso em código C ++ normal (não em diretivas de pré-processador), use uma variável C ++ normal, não uma macro de pré-processador.
Jonathan Wakely
Exceto usando macros variadic, principalmente o uso de macro para chaves de compilador, mas tentar substituir as instruções de macro atuais (como condicionais, chaves de string literal) lidando com instruções de código reais com constexpr é uma boa ideia?
Eu diria que as opções do compilador também não são uma boa ideia. No entanto, eu entendo perfeitamente que é necessário algumas vezes (também macros), especialmente quando se trata de plataforma cruzada ou código incorporado. Para responder à sua pergunta: Se você já está lidando com pré-processador, eu usaria macros para manter claro e intuitivo o que é pré-processador e o que é tempo de compilação. Eu também sugeriria comentar bastante e torná-lo o mais curto e local possível (evite macros espalhadas ou 100 linhas #if). Talvez a exceção seja o guarda #ifndef típico (padrão para # pragma uma vez) que é bem compreendido.
Adrian Maire,
3
Ótima resposta de Jonathon Wakely . Eu também aconselho você a dar uma olhada na resposta de Jogojapan sobre qual é a diferença entre conste constexprantes mesmo de considerar o uso de macros.
As macros são burras, mas no bom sentido. Aparentemente, hoje em dia, eles são um auxílio de construção para quando você deseja que partes muito específicas do seu código sejam compiladas apenas na presença de certos parâmetros de construção sendo "definidos". Geralmente, tudo o que meios está tomando seu nome macro, ou melhor ainda, vamos chamá-lo um Trigger, e as coisas adicionando gosta, /D:Trigger, -DTrigger, etc, para as ferramentas de compilação que está sendo usado.
Embora haja muitos usos diferentes para macros, estes são os dois que vejo com mais frequência que não são práticas ruins / desatualizadas:
Seções de código específicas de hardware e plataforma
Maior verbosidade aumenta
Portanto, embora você possa, no caso do OP, atingir o mesmo objetivo de definir um int com constexprou a MACRO, é improvável que os dois se sobreponham ao usar as convenções modernas. Aqui estão alguns usos de macro comuns que ainda não foram eliminados.
#if defined VERBOSE || defined DEBUG || defined MSG_ALL// Verbose message-handling code here#endif
Como outro exemplo de uso de macro, digamos que você tenha algum hardware para lançar, ou talvez uma geração específica dele que tenha algumas soluções alternativas complicadas que os outros não requerem. Vamos definir essa macro como GEN_3_HW.
#if defined GEN_3_HW && defined _WIN64// Windows-only special handling for 64-bit upcoming hardware#elif defined GEN_3_HW && defined __APPLE__// Special handling for macs on the new hardware#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__// Greetings, Outlander! ;)#else// Generic handling#endif
Respostas:
Não. Absolutamente não. Nem mesmo perto.
Além do fato de que sua macro é uma
int
e suaconstexpr unsigned
é umaunsigned
, existem diferenças importantes e as macros têm apenas uma vantagem.Escopo
Uma macro é definida pelo pré-processador e é simplesmente substituída no código toda vez que ocorre. O pré-processador é burro e não entende a sintaxe ou semântica do C ++. As macros ignoram escopos, como namespaces, classes ou blocos de função, portanto, você não pode usar um nome para qualquer outra coisa em um arquivo de origem. Isso não é verdade para uma constante definida como uma variável C ++ adequada:
#define MAX_HEIGHT 720 constexpr int max_height = 720; class Window { // ... int max_height; };
É bom ter uma variável de membro chamada
max_height
porque é um membro de classe e, portanto, tem um escopo diferente e é diferente daquele no escopo do namespace. Se você tentar reutilizar o nomeMAX_HEIGHT
do membro, o pré-processador irá alterá-lo para este absurdo que não compilaria:class Window { // ... int 720; };
É por isso que você deve fornecer macros
UGLY_SHOUTY_NAMES
para garantir que eles se destaquem e você pode ter cuidado ao nomeá-los para evitar conflitos. Se você não usa macros desnecessariamente, não precisa se preocupar com isso (e não precisa lerSHOUTY_NAMES
).Se você quer apenas uma constante dentro de uma função, você não pode fazer isso com uma macro, porque o pré-processador não sabe o que é uma função ou o que significa estar dentro dela. Para limitar uma macro a apenas uma determinada parte de um arquivo, você precisa
#undef
dela novamente:int limit(int height) { #define MAX_HEIGHT 720 return std::max(height, MAX_HEIGHT); #undef MAX_HEIGHT }
Compare com o muito mais sensato:
int limit(int height) { constexpr int max_height = 720; return std::max(height, max_height); }
Por que você prefere o macro?
Um local de memória real
Uma variável constexpr é uma variável, então ela realmente existe no programa e você pode fazer coisas normais em C ++ como pegar seu endereço e vincular uma referência a ele.
Este código tem comportamento indefinido:
#define MAX_HEIGHT 720 int limit(int height) { const int& h = std::max(height, MAX_HEIGHT); // ... return h; }
O problema é que
MAX_HEIGHT
não é uma variável, portanto, para a chamada destd::max
um temporárioint
deve ser criada pelo compilador. A referência que é retornada porstd::max
pode então referir-se àquele temporário, que não existe após o final dessa instrução, entãoreturn h
acessa a memória inválida.Esse problema simplesmente não existe com uma variável adequada, porque tem um local fixo na memória que não desaparece:
int limit(int height) { constexpr int max_height = 720; const int& h = std::max(height, max_height); // ... return h; }
(Na prática, você provavelmente
int h
não declararia,const int& h
mas o problema pode surgir em contextos mais sutis.)Condições do pré-processador
O único momento para preferir uma macro é quando você precisa que seu valor seja compreendido pelo pré-processador, para uso em
#if
condições, por exemplo#define MAX_HEIGHT 720 #if MAX_HEIGHT < 256 using height_type = unsigned char; #else using height_type = unsigned int; #endif
Você não pode usar uma variável aqui, porque o pré-processador não entende como se referir às variáveis pelo nome. Ele só entende coisas básicas muito básicas, como expansão macro e diretivas que começam com
#
(como#include
e#define
e#if
).Se você quiser uma constante que possa ser entendida pelo pré - processador , você deve usar o pré-processador para defini-la. Se você quiser uma constante para o código C ++ normal, use o código C ++ normal.
O exemplo acima é apenas para demonstrar uma condição de pré-processador, mas mesmo esse código pode evitar o uso do pré-processador:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
fonte
constexpr
variável não precisa ocupar memória até que seu endereço (um ponteiro / referência) seja obtido; caso contrário, pode ser totalmente otimizado (e acho que pode haver o Standardese que garante isso). Quero enfatizar isso para que as pessoas não continuem usando o velho e inferior 'enum
hack' de uma ideia equivocada de que um trivialconstexpr
que não requer armazenamento irá ocupar algum.int height
seria tão problemático quanto a macro, já que seu escopo está vinculado à função, essencialmente temporário. 3. O comentário acima, "const int & h irá estender a vida útil do temporário" está correto.limit
, o problema é o valor de retorno destd::max
. 2. sim, por isso não retorna uma referência. 3. errado, veja o link coliru acima.const int& h = max(x, y);
emax
retornar pelo valor, a vida útil do valor de retorno será estendida. Não pelo tipo de retorno, mas pelo aoconst int&
qual está vinculado. O que escrevi está correto.De modo geral, você deve usar
constexpr
sempre que puder e macros apenas se nenhuma outra solução for possível.Justificativa:
As macros são uma simples substituição no código e, por esse motivo, costumam gerar conflitos (por exemplo,
max
macro windows.h vsstd::max
). Além disso, uma macro que funciona pode ser facilmente usada de uma maneira diferente, o que pode desencadear erros de compilação estranhos. (por exemplo,Q_PROPERTY
usado em membros da estrutura)Devido a todas essas incertezas, é um bom estilo de código evitar macros, exatamente como você normalmente evitaria gotos.
constexpr
é semanticamente definido e, portanto, normalmente gera muito menos problemas.fonte
#if
coisas para as quais o pré-processador é realmente útil. Definir uma constante não é uma das coisas para as quais o pré-processador seja útil, a menos que essa constante deva ser uma macro porque é usada em condições de pré-processador#if
. Se a constante for para uso em código C ++ normal (não em diretivas de pré-processador), use uma variável C ++ normal, não uma macro de pré-processador.Ótima resposta de Jonathon Wakely . Eu também aconselho você a dar uma olhada na resposta de Jogojapan sobre qual é a diferença entre
const
econstexpr
antes mesmo de considerar o uso de macros.As macros são burras, mas no bom sentido. Aparentemente, hoje em dia, eles são um auxílio de construção para quando você deseja que partes muito específicas do seu código sejam compiladas apenas na presença de certos parâmetros de construção sendo "definidos". Geralmente, tudo o que meios está tomando seu nome macro, ou melhor ainda, vamos chamá-lo um
Trigger
, e as coisas adicionando gosta,/D:Trigger
,-DTrigger
, etc, para as ferramentas de compilação que está sendo usado.Embora haja muitos usos diferentes para macros, estes são os dois que vejo com mais frequência que não são práticas ruins / desatualizadas:
Portanto, embora você possa, no caso do OP, atingir o mesmo objetivo de definir um int com
constexpr
ou aMACRO
, é improvável que os dois se sobreponham ao usar as convenções modernas. Aqui estão alguns usos de macro comuns que ainda não foram eliminados.#if defined VERBOSE || defined DEBUG || defined MSG_ALL // Verbose message-handling code here #endif
Como outro exemplo de uso de macro, digamos que você tenha algum hardware para lançar, ou talvez uma geração específica dele que tenha algumas soluções alternativas complicadas que os outros não requerem. Vamos definir essa macro como
GEN_3_HW
.#if defined GEN_3_HW && defined _WIN64 // Windows-only special handling for 64-bit upcoming hardware #elif defined GEN_3_HW && defined __APPLE__ // Special handling for macs on the new hardware #elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__ // Greetings, Outlander! ;) #else // Generic handling #endif
fonte