17
$\begingroup$

I am developing a code to simulate fluid flow with biological substances present in the flow. This involves the standard Navier-Stokes equations coupled to some additional biological models. There are many parameters/constants.

I have written functions to handle the major computations, but a problem I am having is the large number of constants/parameters that these computations depend on. It seems cumbersome to pass 10-20 arguments to a function.

One alternative is to make all the constants global variables, but I know this is frowned upon in C++.

What is the standard way of handling many inputs to a function? Should I make a struct and pass that instead?

Thank you

$\endgroup$
  • 7
    $\begingroup$ If it's possible, try to have the constants evaluated at compile time using constexpr. I try to include most of these in a separate header file. For variables, I have found that a separate class has benefits, but at the cost of potentially more bugs because you have to initialize the class before passing into the function. $\endgroup$ – Biswajit Banerjee Feb 4 at 18:36
  • 3
    $\begingroup$ This is hard to answer properly without some kind of a code sample. Should I make a struct and pass that instead? In general, yes, this is absolutely the usual way to go. Group the parameters/constants by their meaning. $\endgroup$ – Kirill Feb 5 at 1:37
  • 1
    $\begingroup$ "One alternative is to make all the constants global variables, but I know this is frowned upon in C++" Is it? $\endgroup$ – Lightness Races in Orbit Feb 5 at 16:26
  • 1
    $\begingroup$ Are they really, really constants? What if you want to apply your model in a different domain? I would recommend putting them in a little class. That at least gives you a little bit of flexibility in the future $\endgroup$ – André Feb 5 at 19:08
  • $\begingroup$ @André Most of them are user-controlled via a parameter file, which is why I would agree that the class solution is best. $\endgroup$ – EternusVia Feb 5 at 20:03
12
$\begingroup$

If you have constants that will not change before runs, declare them in a header file:

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

The reason why you would want to do this is that it allows the compiler to calculate constant values ahead before run-time, which is good if you have a lot of them.

You can also use a simple class to pass values around:

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);
}
$\endgroup$
  • $\begingroup$ All great answers but the class-solution works best for my situation. $\endgroup$ – EternusVia Feb 5 at 4:36
  • 8
    $\begingroup$ If you make the variables global constexpr ones, at least enclose them into a namespace so they don't step on any other global symbols. Using a global variable called G is just calling for trouble. $\endgroup$ – Wolfgang Bangerth Feb 5 at 13:48
  • 1
    $\begingroup$ Why do you lead include guards with _? You should never be writing anything that begins with _, you risk collision with compiler vars. You should be doing something like ifndef PROJECT_NAME_FILE_NAME_EXTENSION. Also, not sure why you capitalized constants, but not your include guard macros. You generally want to capitalize all macros, especially because they are not sanitary. For constants capitalization doesn't make sense in general. G is fine because its SI, but mass_earth is more appropriate, and should be qualified with a namespace to signify global ie constants::mass_earth. $\endgroup$ – opa Feb 5 at 14:52
9
$\begingroup$

Another alternative that may be in line with your train of thought is to use a namespace (or nested namespaces) to properly group constants. An example might be:

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 density = 0.999; // g/cm^3
      constexpr dyn_viscosity = 1.6735; //mPa * s
   }// constants about fluid at 2C

   // ... 

} // end namespace for constants

Using the above technique, you can localize reference constants to some desired files and namespaces, making them more controlled than global variables while getting some of the similar benefits. When you use the constants, it is as simple as doing:

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

If you dislike long chains of nested namespaces, you can always shorten things when necessary by using a namespace alias:

namespace const_earth = constants::earth;
constexpr double G_times_2 = 2.0*const_earth::G;
$\endgroup$
1
$\begingroup$

One way that I do is to use singleton.

When you start your program you initiate your singleton and fill it with the constant data (probably from a properties file that you have for the run). You get this in every class that you need the values and just use it.

$\endgroup$
  • $\begingroup$ Warning: I've occasionally had singletons serialize accesses in multi-threaded code. So you may want to check on this as part of your profiling stage. $\endgroup$ – Richard Feb 4 at 23:27
  • $\begingroup$ I would certainly not put them in a singleton... In practice those constants will change in the future when (not if) you apply your model in a different domain. Having them a singleton makes it very hard to then test with different parameters. $\endgroup$ – André Feb 5 at 19:06
  • $\begingroup$ They're all constants. There's no need here for a singleton. A static accessor class is a better use here. Even better would be a static class where the values are pulled from a config file (so if your end-user sees there's a mistake, or wants more precision, they can adjust the config file without getting a new build). $\endgroup$ – Scuba Steve Feb 5 at 19:11
  • $\begingroup$ Singletons are rarely, if ever, a good idea. Dependency injection is a much cleaner and more flexible solution. However, with just constants, I'd say just keep them constants in a header somewhere. $\endgroup$ – mascoj Feb 5 at 23:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.