Your question gives a confused impression to me, because I am not sure which actual problem you are trying to solve.
You showed us
//version 1
void foo(int i)
// version 1.1
void foo(int i, bool enablethis=true)
Assumed the behaviour of foo(x,true)
is the same as the behaviour of version 1.0 of foo(x)
, there no issue, the code behaves backwards compatible and no caller has to be changed.
Then you added
Version 1.2 forces me to change the function to
void foo(int i, std::string whatever, bool enablethis=true)
Taking this literally, this could break backwards compatibility, because any caller now has to provide a string parameter whatever
. If this is an issue or not depends a lot on the real context, especially how much control you have over the calling code, and what semantics the parameter whatever
introduces. Some possible cases:
You have the code which calls foo
fully under your control, and you want all places where foo
is called to add a sensible value for whatever
in a deliberate manner. Then go ahead, the compiler will to tell you where you might have forgotten to extend a call to foo
, and the signature change is not an issue, quite the opposite.
You would prefer the existing callers to be untouched, maybe because you don't have all the calling code under your control, or because changing all the calls would imply too much testing effort. Then you could change the order of arguments and provide a default value for whatever
.
foo(int i, bool enablethis=true, std::string whatever="")
This will allow you not to think about the callers foo(x,false)
, which can remain untouched.
Now, how does this compare to the idea of using a helper class InputParameters
? In fact, this does not gain anything in regards to backwards compatibility. If your callers have to initialize an object of type InputParameters
before calling foo
, extending it with another non-optional attribute whatever
will just shift the need for change from the call to foo
to the caller's initialization code in front of the call to foo
. And in case whatever
can be made optional by providing a default value in the constructor of InputParameters
, it does not make a huge difference to the variant #2 I scetched above. Assumed you add proper constructors to InputParameters
, the calling side would look something like this:
// instead of foo(i)
InputParameters p(i);
foo(p);
// instead of foo(i,enablethis);
InputParameters p(i,enablethis);
foo(p);
// instead of foo(i,enablethis, whatever);
InputParameters p(i,enablethis,whatever);
foo(p);
So when does it really make sense to bundle several input parameters of a function into a new class or data structure? A growing number of arguments can be indeed a code smell, but the real issue here is neither backwards compatibility nor scalability, but readability and maintainability. And for this, it is important not to bundle mechanically every parameter in some class with a nonsensical name like InputParameters
. That would cause the opposite of what you are trying to achieve, since whenever one now will try to call foo, they first look at the signature, see that there is some black box InputParameters
there, and have to lookup that class will require and which members it contains.
Instead, group several parameters together in one helper class if you can give them a sensible, common name, some superordinate term which tells readers what the class represents. This is also called "creating an abstraction". If you cannot come up with a sensible name for a group of parameters, because there is no logical grouping, you better leave them separated.
Of course, there may be more driving factors here: another motivation for creating a class InputParameters
(hopefully with a clearer name) could be that it makes it possible to add some code here, for example, default initialization code for some of the members which were too complex to be provided in the signature of foo
. Or, you know that the caller may want to separate calling from initialization. Or, as others have mentioned, InputParameters
is just some base class in a class hierarchy of different kinds of input parameters.
So in short, bundling input parameters of a function into a new class can make sense, but not for the reasons you probably had in mind when asking this question, and not just "in general" - it depends all on the specific context.
InputParameters
instance. Unless this an opaque value created and provided by your own application, you have just moved the problem instead of solving it.