10

If I add a move-constructor (or move-assignment operator) to my library, will I break binary-compatibility? Can that addition break a user's code in any way?

class Foo {
public:
  Foo();
  Foo(Foo const&);
  Foo& operator=(Foo const&);

// new methods:
  Foo(Foo&&);
  Foo& operator=(Foo&&);
};
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
cdunn2001
  • 17,657
  • 8
  • 55
  • 45
  • if it was, would it go inside the standard in the first place? – David Haim Apr 28 '15 at 08:41
  • Isn't this compiler-specific ? There's no compiler mentioned. – Quentin Apr 28 '15 at 08:44
  • 1
    Our library (**jsoncpp**) supports many compilers. I'm pretty sure we're ok on binary-compatibility, but maybe there is a corner case of move-semantics which could break existing source-code in some other way. – cdunn2001 Apr 28 '15 at 08:52

2 Answers2

1

In my opinion as long as you don't add a member or a virtual function there shouldn't be any effect on binary compatibility since the layout of the object does not change.

If one component (say a shared library, .dll on windows or .so on Linux) uses the old version of the library then it will copy all instances of the object (even rvalues) regardless of whether it was created by a component using the new library (and vice versa).

As long as move semantics are used in order to improve performance and therefore the resulting moved objects behave the same as copied objects there should be no problem. The only differences would be improved performance caused by fewer calls to memory [de]allocations and copies (etc). If move operations are used to make different semantics (a moved object differs from a copied object) then all bets are off but I don't think anyone would do this on purpose (except perhaps for job security).

As long as the binary layout of the object doesn't change I don't see how any breakage can be introduced.

Motti
  • 110,860
  • 49
  • 189
  • 262
1

It definitely breaks binary compatibility in one direction: code compiled against your newer library can't work with your old one, since the move constructor won't be found when linking.

The other direction is trickier. It generally isn't much of a problem, but code could observe at least the presence of the new assignment operator with SFINAE tricks, and you end up with a program where some parts think that operator exists, and other parts don't. This may even cause ODR violations if the same code is compiled twice (the same template instantiation in different translation units). And those ODR violations can cause link-time errors again.

  • I think it's safe to assume that when code is compiled it uses the headers and library from the same version. The question (as I understand it) is would there be a problem when passing an object between binaries that were compiled with different versions of the library. For example, is the ODR true across different binaries? – Motti Apr 28 '15 at 10:30
  • @Motti A binary "A" may use a library "B" and a library "C", where library "B" also uses library "C". Binary "A" and library "B" may be compiled against different versions of library "C", and then you get the problems I described even when you only have a single program. –  Apr 28 '15 at 10:33
  • If I understand what you're saying then (baring the user being too nosey for his own good) any problems will cause the program not to link and will be resolved by linking to the later version of the library. – Motti Apr 28 '15 at 19:12
  • @Motti No, it can cause the program not to link, but it can also cause the program to link without errors, and behave unexpectedly at run time. –  Apr 28 '15 at 20:23
  • How so? could you expand your answer to include an explanation? – Motti Apr 28 '15 at 20:29
  • @Motti Except for a typo (now fixed), I did attempt to include an explanation already: if the same template is instantiated twice, and those template instantiations aren't identical, then at run time, if linking succeeds, almost certainly one instantiation will be picked: they can't generally both be present if they have the same mangled name. Code may be expecting the other instantiation, though. –  Apr 28 '15 at 20:31
  • I'm sorry I must be extraordinarily thick today. If a type now has a move constructor and is used in a template that was called by code that was compiled with the old version that didn't have said move constructor, then this code may get an instantiation of the template that uses the move constructor (which is linked to in the new library). How can this cause unexpected behaviour? (assuming that move constructor has the regular semantics and isn't doing something evil) – Motti Apr 28 '15 at 20:36
  • @Motti Suppose some common utility library has this (silly) template function: `template T f(T& t) { return T(std::move(t)); }` Suppose binary "A" instantiates `f` with the old version of your library "C", and gets the copy constructor. Suppose library "B" instantiates `f` as well, but with the new version of your library, and gets the move constructor. Now what happens when binary "A" gets linked with library "B"? You can't have multiple `f` instantiations in the same program, so one version gets picked, probably the binary's. And that gets called even from the library. –  Apr 28 '15 at 20:49
  • @Motti (Assuming you get no error message, anyway.) But library "B" may have been relying on the move effect of `f`, and it would be completely within its rights to do so. –  Apr 28 '15 at 20:53