This question is intended as a follow up question to this one: What are the differences between a pointer variable and a reference variable in C++?
Having read the answers and some further discussions I found on stackoverflow I know that the compiler should treat pass-by-reference the same way it treats pass-by-pointer and that references are nothing more than syntactic sugar. One thing I haven't been able to figure out yet if there is any difference considering binary compatibility.
In our (multiplatform) framework we have the requirement to be binary compatible between release and debug builds (and between different releases of the framework). In particular, binaries we build in debug mode must be usable with release builds and vice versa. To achieve that, we only use pure abstract classes and POD in our interfaces.
Consider the following code:
class IMediaSerializable
{
public:
virtual tResult Serialize(int flags,
ISerializer* pSerializer,
IException** __exception_ptr) = 0;
//[…]
};
ISerializer and IException are also pure abstract classes. ISerializer must point to an existing object, so we always have to perform a NULL-pointer check. IException implements some kind of exception handling where the address the pointer points to must be changed. For this reason we use pointer to pointer, which must also be NULL-pointer checked.
To make the code much clearer and get rid of some unnecessary runtime checks, we would like to rewrite this code using pass-by-reference.
class IMediaSerializable
{
public:
virtual tResult Serialize(int flags,
ISerializer& pSerializer,
IException*& __exception_ptr) = 0;
//[…]
};
This seems to work without any flaws. But the question remains for us whether this still satisfies the requirement of binary compatibility.
UPDATE: To clarify things up: This question is not about binary compatibility between the pass-by-pointer version of the code and the pass-by-reference version. I know this can't be binary compatible. In fact we have the opportunity to redesign our API for which we consider using pass-by-reference instead of pass-by-pointer without caring about binary compatibilty (new major release). The question is just about binary compatibility when only using the pass-by-reference version of the code.
Generally references are implemented as pointers under-the-hood, so there will usually be ABI compatibility. You will have to check your particular compiler's documentation and possibly implementation to make sure.
However, your restriction to pure-abstract classes and POD types is over zealous in the age of C++11.
C++11 split the concept of pod into multiple pieces. Standard Layout covers most, if not all, of the "memory layout" guarantees of a pod type.
But Standard Layout types can have constructors and destructors (among other differences).
So you can make a really friendly interface.
Instead of a manually managed interface pointer, write a simple smart pointer.
that
->clone()s on copy, moves the pointer on move, deletes on destroy, and (because you own it) can be guaranteed to be stable over compiler library revisions (whileunique_ptrcannot). This is basically aunique_ptrthat supports->clone(). Also have your ownunique_ptrfor values that cannot be duplicated.Now you can replace your pure virtual interfaces with a pair of types. First, the pure virtual interface (with a
T* clone() constusually), and second a regular type:the end result is you have a type that behaves like a regular, everyday type, but it is implemented as a wrapper around a pure virtual interface class. Such types can be taken by value, taken by reference, and returned by value, and can hold arbitrary complex state within them.
These types live in header files that the library exposes.
And interface expansion of
IFoois fine. Just add a new method to both theIFooat the end of the type (which under most ABIs is backward compatible (!) -- try it), then add a new method tomy_regular_foothat forwards to it. As we did not change the layout of ourmy_regular_foo, even though the library and the client code may disagree about what methods it has, that is fine -- those methods are all compiled inline and never exported -- and clients who know they are using the newer version of your library can use it, and those who do not know but are using it are fine (without rebuilding).There is one careful gotcha: if you add an overload to
IFooof a method (not an override: an overload) the order of the virtual methods changes, and if you add a newvirtualparent the layout of the virtual table can change, and this only works reliably if all inheritance to your abstract classes isvirtualin your public API (with virtual inheritance, the vtable has pointers to the start of each vtable of the sub-classes: so each sub-class can have a bigger vtable without messing up the address other functions virtual functions. And if you carefully only append to the end of a sub-class vtable code using the earlier header files can still find the earlier methods).This last step -- allowing new methods on your interfaces -- might be a bridge to far, as you'd have to investigate the ABI guarantees (in practice and not) on vtable layout for every supported compiler.