Search code examples
c++backwards-compatibility

Extend an existing API: Use default argument or wrapper function?


I have an existing method (or function in general) which I need to grow additional functionality, but I don't want to break any use of the method elsewhere in the code. Example:

int foo::bar(int x)
{
 // a whole lot of code here
 return 2 * x + 4;
}

is widely used in the codebase. Now I need to make the 4 into a parameter, but any code that already calls foo::bar should still receive what it expects. Should I extend and rename the old method and wrap it into a new one like

int foo::extended_bar(int x, int y)
{
 // ...
 return 2 * x + y;
}

int foo::bar(int x)
{
 return extended_bar(x,4);
}

or should I declare a default argument in the header file like

int bar(int x, int y=4);

and just extend the function

int foo::bar(int x, int y)
{
 // ...
 return 2 * x + y;
}

What are advantages and disadvantages of each variant?


Solution

  • I usually use a wrapper function (via overloading most of the time) instead of default parameters.

    The reason is that there are two levels of backward compatibility:

    1. Having source-level backward compatibility means that you have to recompile the calling code without changes, because the new function signatures are compatible to the old ones. This level can be achieved with both; default values and wrappers/overloading.

    2. A stronger level is binary-level backward compatibility, which even works without recompilation, e.g. when you don't have access to the calling code. Imagine you deploy your function in binary form, like in a DLL, etc. In such a case, the signatures have the be exactly the same to make it work, which is not the case for default values - they will break this level of compatibility.

    Another advantage of wrapper functions is - if your application has logging of any kind - you can dump out a warning in the old function that it will become obsolete in future versions and that it is recommended to use the new one.