Situation: We have a C++ library "L" with a class X that has already some pure virtual methods. The library "L" provides all derived classes for X that are needed in production code - users do not have to create derived classes of X themselves. However, for testing, users of "L" have created derived classes of X to mock "L" in their unit-tests.
Problem: X shall be extended by adding a new pure virtual method. It is clear that this breaks binary compatibility of user code, but that is not our concern. We only strive for source code compatibility of user code. This source code compatibility of user code is granted for production code. However, the unit-tests of the users break, because their mock classes that are derived from the changed X can no longer be instantiated.
We are looking for a way to introduce the new method in a migration friendly way that does avoid breaking user code - be it production code or test code. In other scenarios where interfaces changed we used the common approach to have two steps: first deprecate some function, then remove it in a later release. That is, we are willing to take an approach that involves more than one step.
For example, we were thinking about adding the new method as non-pure at first (still allowing the derived mock classes to be instantiated), and make the new method pure later. However, those users who do not anyway want to use the new method would not get any indication that their test classes need adaptation: There does not seem to be a way to mark a method in a way that indicates "the use as a non-pure function is deprecated".
We are aware of some approaches to solve such problems:
- A new class derived from
Xcould be created to hold the pure new virtual method, such that users of the new method would derive from that (Extending a class and maintaining binary backward compatibility). - We could provide - for mocking purposes - some class
XMockBasewhich is derived fromXand has some default implementation for each method, such that implementers of mock classes would not derive fromXbut rather fromXMockBase.
We may in the end take one of those approaches. But, before going in such a direction we would like to know:
Is there any migration friendly approach that can be used to introduce the new method into the existing class?
If you include this with
USE_OLD_BOB_APIdefined,TheLibrary::Bobrefers to a[[deprecated]]class with onlyCountAlicesas a pure-virtual function, andCountCharlieshas afinaldefault implementation. So existing code has compile time compatibility.If you include this without
USE_OLD_BOB_APIdefined,TheLibrary::Bobrefers to aBobwith a pure virtualCountCharlies.So you'll get a
[[deprecated]]warning in the code, but it won't otherwise break.Logic to define
USE_OLD_BOB_APIis pretty standard versioning macro stuff I think: you'll ask them to define aTHELIBRARY_VERSION_NUMBERmacro; if THAT is undefined, it will use the oldest version of your API.Code can still directly access the specific versions of
Bobeven after they have updated the library version, in case they are doing a partial update. So if there are 3 such classes, they can bump the version number up, get the new APIs, and manually put inTheLibrary::v1::Bobbecause they are going to get around toBoblast.So long as they are using old versions of
Bobthey'll bedeprecated.Now, the real problem is that your customers will insist on compling with warnings as errors. And you cannot communicate a non-fatal error when they do so.
Dirk has incidated he didn't understand how to map
USE_OLD_BOB_APIto a concrete case, so I'll do it.