Práticas recomendadas de C ++ para lidar com muitas constantes e variáveis ​​em códigos científicos

17

Estou desenvolvendo um código para simular o fluxo de fluidos com substâncias biológicas presentes no fluxo. Isso envolve as equações padrão de Navier-Stokes acopladas a alguns modelos biológicos adicionais. Existem muitos parâmetros / constantes.

Eu escrevi funções para lidar com os principais cálculos, mas um problema que estou tendo é o grande número de constantes / parâmetros dos quais esses cálculos dependem. Parece complicado passar de 10 a 20 argumentos para uma função.

Uma alternativa é tornar todas as constantes variáveis ​​globais, mas sei que isso é desaprovado em C ++.

Qual é a maneira padrão de lidar com muitas entradas para uma função? Devo fazer uma estrutura e passar isso em vez disso?

Obrigado

EternusVia
fonte
7
Se possível, tente avaliar as constantes em tempo de compilação usando constexpr. Eu tento incluir a maioria deles em um arquivo de cabeçalho separado. Para variáveis, descobri que uma classe separada tem benefícios, mas à custa de potencialmente mais erros, porque você precisa inicializar a classe antes de passar para a função.
Biswajit Banerjee
3
É difícil responder adequadamente sem algum tipo de amostra de código. Devo fazer uma estrutura e passar isso em vez disso? Em geral, sim, este é absolutamente o caminho usual a seguir. Agrupe os parâmetros / constantes pelo seu significado.
quer
11
"Uma alternativa é tornar todas as constantes variáveis ​​globais, mas eu sei que isso é desaprovado em C ++" É?
Lightness Races com Monica
11
Eles são realmente, realmente constantes? E se você quiser aplicar seu modelo em um domínio diferente? Eu recomendaria colocá-los em uma turma pequena. Isso pelo menos dá a você um pouco de flexibilidade no futuro.
André
@ André A maioria deles é controlada pelo usuário através de um arquivo de parâmetro, e é por isso que eu concordo que a solução da classe é a melhor.
EternusVia

Respostas:

13

Se você tiver constantes que não serão alteradas antes das execuções, declare-as em um arquivo de cabeçalho:

//constants.hpp
#ifndef PROJECT_NAME_constants_hpp
#define PROJECT_NAME_constants_hpp
namespace constants {
  constexpr double G        = 6.67408e-11;
  constexpr double M_EARTH  = 5.972e24;
  constexpr double GM_EARTH = G*M_EARTH; 
}
#endif

//main.cpp
using namespace constants;
auto f_earth = GM_EARTH*m/r/r;  //Good
auto f_earth = G*M_EARTH*m/r/r; //Also good: compiler probably does math here too

A razão pela qual você deseja fazer isso é que ele permite que o compilador calcule valores constantes antes do tempo de execução, o que é bom se você tiver muitos deles.

Você também pode usar uma classe simples para passar valores:

class Params {
 public:
  double a,b,c,d;
  Params(std::string config_file_name){
    //Load configuration here
  }
};

void Foo(const Params &params) {
  ...
}

int main(int argc, char **argv){
  Params params(argv[1]);
  Foo(params);
}
Richard
fonte
Todas ótimas respostas, mas a solução de classe funciona melhor para a minha situação.
EternusVia
8
Se você tornar as variáveis ​​globais constexpr, ao menos coloque-as em um namespacepara que elas não pisem em nenhum outro símbolo global. Usar uma variável global chamada Gé apenas um problema.
Wolfgang Bangerth 5/02/19
11
Por que você lidera inclui guardas com _? Você nunca deve escrever nada que comece com _, corre o risco de colisão com os vars do compilador. Você deveria estar fazendo algo parecido ifndef PROJECT_NAME_FILE_NAME_EXTENSION. Além disso, não sei por que você colocou constantes em maiúsculas, mas não as macros de guarda de inclusão. Você geralmente deseja capitalizar todas as macros, especialmente porque elas não são sanitárias. Para constantes, a capitalização não faz sentido em geral . Gé bom porque seu SI, mas mass_earth é mais apropriado e deve ser qualificado com um espaço para nome para significar ie global constants::mass_earth.
whn
12

Outra alternativa que pode estar alinhada com sua linha de pensamento é usar um espaço para nome (ou espaços para nome aninhados) para agrupar corretamente as constantes. Um exemplo pode ser:

namespace constants {
   namespace earth {
      constexpr double G = 6.67408e-11;
      constexpr double Mass_Earth = 5.972e24;
      constexpr double GM = G*Mass_Earth;
   }// constant properties about Earth

   namespace fluid {
      constexpr double density = 0.999; // g/cm^3
      constexpr double dyn_viscosity = 1.6735; //mPa * s
   }// constants about fluid at 2C

   // ... 

} // end namespace for constants

Usando a técnica acima, você pode localizar constantes de referência em alguns arquivos e espaços de nomes desejados, tornando-os mais controlados que as variáveis ​​globais e obtendo alguns dos benefícios semelhantes. Quando você usa as constantes, é tão simples quanto fazer:

constexpr double G_times_2 = 2.0*constants::earth::G;

Se você não gosta de longas cadeias de namespaces aninhados, sempre pode encurtar as coisas quando necessário, usando um alias de namespace:

namespace const_earth = constants::earth;
constexpr double G_times_2 = 2.0*const_earth::G;
spektr
fonte
2
Esta é uma abordagem, seguida pelo OpenFOAM , veja um exemplo aleatório do código fonte do OpenFOAM . OpenFOAM é uma biblioteca de códigos C ++ que implementa o método de volume finito, amplamente utilizado na dinâmica de fluidos.
Dohn Joe
1

Uma maneira que eu faço é usar o singleton.

Ao iniciar seu programa, você inicia seu singleton e o preenche com os dados constantes (provavelmente de um arquivo de propriedades que você possui para a execução). Você obtém isso em todas as classes que precisa dos valores e apenas o usa.

Ashkan
fonte
Aviso: Ocasionalmente, tive singletons para serializar acessos em código multithread. Portanto, convém verificar isso como parte do seu estágio de criação de perfil.
Richard
Certamente eu não os colocaria em um singleton ... Na prática, essas constantes mudarão no futuro quando (e não se) você aplicar seu modelo em um domínio diferente. Ter um singleton dificulta o teste com parâmetros diferentes.
André
São todas constantes. Não há necessidade aqui de um singleton. Uma classe de acessador estático é um uso melhor aqui. Melhor ainda seria uma classe estática, na qual os valores são extraídos de um arquivo de configuração (por isso, se o usuário final perceber que há um erro ou desejar mais precisão, eles poderão ajustar o arquivo de configuração sem obter uma nova compilação).
Mergulhador Steve
Singletons raramente são, se é que alguma vez são, uma boa ideia. A injeção de dependência é uma solução muito mais limpa e flexível. No entanto, com apenas constantes, eu diria apenas mantê-las constantes em um cabeçalho em algum lugar.
Mascrej