3

We are developing complex application on the top of the ROS framework in C++ and recently ran into discussion how to provide parameters to the parts of code far away from the starting main().

The application is a kind of a server. It has a listener for incoming requests that then finds a suitable factor for the worker capable of handling the request. The factory then creates the worker (not always the same class) and at this point the worker internals (various methods inside) need parameters that may differ for each worker. The parameters are initially within the configuration file on the file system (same for all objects, with different sections), but they are automatically loaded and parsed as the main node starts.

The parameters are easily accessible in the main() via "node handle" that allows to access them by name, not unlike the system properties are usually accessed. The parameter has one of the few supported primitive types. The type that is differentiated by the name of the method used to access it. Here is the tutorial explaining how the parameters are accessed.

Different developers so far had four approaches:

  1. Pass the "parameter handle" as a parameter to about any constructor, making sure each object has access to it. However this limits testability - a "live" handle an complex interface to the whole ROS infrastructure that must be running. Hence any object that relies on it just to be created is not testable with simple Unit tests.
  2. Read all required parameters within main() after startup and store either as global variables in class-specific namespace or alternatively as static fields of these classes. This contradicts generic understanding that "global variables are bad". While the variables are not modified after being set on startup, looks like they cannot be declared const at the language level due the need to set them once inside the main.
  3. Create the structures containing parameters and pass these around till they reach the constructor where they are used. Different classes need different parameters so we would need to define multiple helper structures, have the code to populate them from node handle and still looks annoying to pass them around.
  4. Create the "parameter provider" class that can be either mock (returning the agreed values) or production implementation (wrapping node handle). Most of the developers are used to access node handle directly so this is promising the long way to persuade them to use that mediating wrapper instead.
  5. Some proposed, use dependency injection framework. I understand Spring would do for Java but does such a thing exists or is even possible in C++?

Which of these four approaches (or probably some other) would be the most optimal to provide parameters from the node handle far away from where the node handle is easily accessible?

1
  • 1
    In my company we usually use approach 3. If some class Foo needs configuration, there is an according FooConfig struct. Then we use a framework like boost serialization to read/write the config. In main, we pass the parsed config structs to the according classes. I like the following advantages: The config of each class is a struct, which makes it easy to use and easy to use a different config in testing. The serialization framework decouples the config struct from the concrete format (xml, binary, etc).
    – pschill
    Jan 28, 2019 at 10:28

3 Answers 3

2

This is somewhat subjective of course, but all of the listed solutions boil down to providing a global access to all parameters to virtually everybody. Effectively making them static/global variables.

There is a 5. solution that is more in line with oo concepts: Refactor/redesign code until all relevant configurable concepts are represented in main().

How is that possible for classes instantiated deep in the application? Redesign, pass a factory to the thing that instantiates it. You can then configure the factory with parameters in the main().

In the dependency-injection domain there is a similar thing called "Propagating dependencies". How to pass stuff directly to the things that need it, without bothering others.

There are several considerations at play:

  • Classes should know only the things they directly need. Things they just pass along is cruft and should be removed.
  • Classes should not know how config works in the application, nor should they know some config "framework" or classes.
  • Configuration values should be normal constructor or method parameters for classes. They should generally not be pulled from some other location.

Summary: You plug your system together in the main() method and it should be configured there also. If that is not possible, redesign until it is.

3
  • +1 this is the right way to inject dependencies. It seems like the OP already has factory classes to create workers, so it shouldn't require too much restructuring to give each factory the required configuration.
    – casablanca
    Jan 30, 2019 at 2:55
  • Thanks, sound great but you are talking as if about Spring framework with its dependency injections. We deal with C++ code here, and this code is already running withing a framework that would not yield easily inverse of control.
    – h22
    Jan 30, 2019 at 8:12
  • @h22 I think this should apply to all oo code in general, regardless whether a DI library is used or not. The only thing that does prevent a proper design is frameworks that insist on instantiating classes for you, because then you are no longer in control of your own objects. Ironically DI frameworks often do that, and prevent a proper design. Jan 30, 2019 at 8:57
1

In a lot of frameworks, configurations are indeed just static (more or less global). Just initialize some sort of a Singleton before everything begins in main(), and then do something like Configurations::GetBool("foo") wherever needed. It should be safe, since this static config object can (and must) be immutable after the initial initialization. Many of the problems with globals and singletons don't apply if you can't set them, and if they are thread safe.

It seems like nowadays, dependency injection is being favored over the static data. Then, you could just create the config provider class that receives a node handle, and inject this config class everywhere you need it.

0

We decided to include an interface in the style of:

class ISettingsConsumer
{
public: 
    virtual std::vector<SettingIdentifier> requiredSettings() = 0;
    virtual void setSetting(SettingIdentifier setting, std::variant value) = 0;
}

By chaining these consumers we're able to define some setting deep in the application (where it is actually required) without cluttering the classes close to the main(). This baseline can ofc be extended with fancy signaling and registering functionality.

In our case this works flawlessly and cleanly if all classes between the settings object and the consumers implement this interface.

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