Preventing compatibility breaks in Symbian C++
This article provides tips for maintaining binary compatibility of Symbian C++ code.
Do not change the size of a class object
You should not change the size of a class object (e.g. by adding or removing data) unless you can guarantee that:
- Your class is not externally derivable
- The only code that allocates an object resides within your component, or it has a non-public constructor that prevents it from being created on the stack
- The class has a virtual destructor.
Do not remove anything accessible
If you remove something from an API which is used by an external component, that component’s code will no longer compile against the API (a break in source compatibility) nor run against its implementation (a break in binary compatibility). Thus, at an API level, you should not remove any externally-visible class, function, enumeration (or any value within an enumeration) or global data, such as string literals or constants. At a class level, you should not remove any methods or class member data. Even private member data should not be removed, in general, because the size of the resulting object will change, which should be avoided for reasons described above.
Do not rearrange accessible class member data
If you don’t remove class member data but merely rearrange it, you can still cause problems to client code that accesses that data directly because, as I described above, the offset of the member data from the object’s this pointer will be changed. You must not change the position of member data in a class if that data is:
- Public (or protected, if the client can derive from the class)
- Exposed through public or protected inline methods (which will have been compiled into client code).
This rule also means that you cannot change the order of the base classes from which a class multiply inherits without breaking compatibility, because this order affects the overall data layout of the derived object.
Do not add, remove or modify the virtual functions of externally derivable classes
If a class is externally derivable (i.e. if it has an exported or inlined, public or protected constructor) you must not alter the nature of the virtual functions in any way, because this will break compatibility with calling code.
Do not rearrange the ordinal of exported functions
Each exported function is associated with an ordinal number, which is used by the linker to identify the function. If you later re-order the .def file, say by adding a new export within the list of current exports, the ordinal number values will change and previously compiled code will be unable to locate the correct function.
Do not Re-order virtual functions
Although not stated in the C++ standard, the order in which virtual member functions are specified in the class definition can be assumed to be the only factor which affects the order in which they appear in the virtual function table. You should not change this order, since client code compiled against an earlier version of the virtual function table will call what has become a completely different virtual function.
Do Not Override a Virtual Function that was Previously Inherited
If you override a virtual function which was previously inherited, you are altering the virtual function table of the class. However, existing client code is compiled against the original vtable and will thus continue to access the inherited base-class function rather than the new, overridden, version. This leads to inconsistency between callers compiled against the original version of the library and those compiled against the new version. Although it does not strictly result in incompatibility, this is best avoided.
Do not modify the documented semantics of an API
If you change the documented behavior of a class or global function, or the meaning of a constant, you may break compatibility with previously published versions used by client code, regardless of whether source and binary compatibility are maintained.
Do not remove const
The semantics of ”const” should not be removed, since this will be a source-incompatible change. This means that you should not remove the const-ness of a parameter, return type or class method that were originally specified as const.
Do not change from pass-by-value to pass-by-reference, or vice versa
If parameters or return values were originally passed by value, you will break binary compatibility if you change them to reference values (or vice versa). When a parameter is passed by value to a function, the compiler generates a stack copy of the entire parameter and passes it to the function. However, if the function signature is changed to accept the parameter by reference, a word-sized reference to the original object is passed to the function instead. The stack frame for a pass-by-reference function call is thus significantly different from that for a pass-by-value function call. In addition, the semantics of passing by value and by reference are very different.