1

In my library there's a class like this:

class Foo {
public:
    void doSomething();
};

Now, implementation of doSomething() has been grow a lot and I want to split it in two methods:

class Foo {
public:
    void doSomething();
private:
    void doSomething1();
    void doSomething2();
};

Where doSomething() implementation is this:

void Foo::doSomething() {
    this->doSomething1();
    this->doSomething2();
}

But now class interface has changed. If I compile this library, all existent applications using this library wont work, external linkage is changed.

How can I avoid breaking of binary compatibility?

I guess inlining solves this problem. Is it right? And is it portable? What happen if compiler optimization uninlines these methods?

class Foo {
public:
    void doSomething();
private:
    inline void doSomething1();
    inline void doSomething2();
};

void Foo::doSomething1() {
    /* some code here */
}

void Foo::doSomething2() {
    /* some code here */
}

void Foo::doSomething() {
    this->doSomething1();
    this->doSomething2();
}

EDIT: I tested this code before and after method splitting and it seems to maintain binary compatibility. But I'm not sure this would work in every OS and every compiler and with more complex classes (with virtual methods, inheritance...). Sometimes I had binary compatibility breaking after adding private methods like these, but now I don't remember in which particular situation. Maybe it was due to symbol tabled looked by index (like Steve Jessop notes in his answer).

Alessandro Pezzato
  • 8,603
  • 5
  • 45
  • 63
  • Why would external linkage be affected by the change in the private portion of the class? – littleadv Dec 07 '11 at 10:33
  • You could have prevented this problem by using Pimpl from the start - it encapsulates such changes. – Björn Pollex Dec 07 '11 at 10:34
  • 3
    @littleadv: there are a lot of changes you can make to the private portion of a class that break binary compatibility. Adding private virtual functions, adding private data members. This happens not to be one of them, at least on implementations I know about, but the fact the function is private has nothing to do with it. – Steve Jessop Dec 07 '11 at 10:38

3 Answers3

2

Strictly speaking, changing the class definition at all (in either of the ways you show) is a violation of the One Definition Rule and leads to undefined behavior.

In practice, adding non-virtual member functions to a class maintains binary compatibility in every implementation out there, because if it didn't then you'd lose most of the benefits of dynamic libraries. But the C++ standard doesn't say much (anything?) about dynamic libraries or binary compatibility, so it doesn't guarantee what changes you can make.

So in practice, changing the symbol table doesn't matter provided that the dynamic linker looks up entries in the symbol table by name. There are more entries in the symbol table than before, but that's OK because all the old ones still have the same mangled names. It may be that with your implementation, private and/or inline functions (or any functions you specify) aren't dll-exported, but you don't need to rely on that.

I have used one system (Symbian) where entries in the symbol table were not looked up by name, they were looked up by index. On that system, when you added anything to a dynamic library you had to ensure that any new functions were added to the end of the symbol table, which you did by listing the required order in a special config file. You could ensure that binary compatibility wasn't broken, but it was fairly tedious.

So, you could check your C++ ABI or compiler/linker documentation to be absolutely sure, or just take my word for it and go ahead.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • So... we are not sure to not break binary compatibilty without knowing compiler/linker behavior. But inlining added functions is always binary compatibilty safe? – Alessandro Pezzato Dec 07 '11 at 13:45
  • @Alessandro: within the library, whether a function is actually inlined into a particular call site or not should make no difference. As the programmer, you have no way of knowing whether a given call will be inlined or not, so it would be pretty harsh if an implementation broke compatibility based on whether or not it actually happens. There's also no good reason for it to make a difference - when the program jumps into the code in the dll, it doesn't really matter whether that dll code then jumps to other places in the dll before returning, or just runs through some inlined code and returns. – Steve Jessop Dec 07 '11 at 13:49
  • Between the caller and the dll things are slightly different. If some function is inlined into the calling executable, and then you change the definition in the dll of that same inline function, then you've broken the ODR again, but this time it might matter -- best case is that the calling code continues executing the old definition. Personally I wouldn't count on that without researching it very carefully, and I'd only even bother checking if absolutely necessary because I was painted into a corner by some earlier design decision. – Steve Jessop Dec 07 '11 at 13:55
0

There is no problem here. The name mangling of Foo::doSomething() is always the same regardless of it's implementation.

Puppy
  • 144,682
  • 38
  • 256
  • 465
0

I think the ABI of the class won't change if you add non-virtual methods because non-virtual methods are not stored in the class object, but rather as functions with mangled names. You can add as many functions as you like as long as you don't add class members.

thiton
  • 35,651
  • 4
  • 70
  • 100