Search code examples
c++gccdynamic-libraryrecompileinline-functions

C++: What to recompile when an inline function in a dynamic library changes?


I have two dynamic libraries and one executable:

  • libOtherLibrary.so
    • This is an existing open-source library written by someone else.
  • libMyLibrary.so
    • This is my own library that depends on libOtherLibrary.so.
  • exe
    • This is my own executable that depends on both libraries.

As a test to see when a specific function is called, I added a print statement to an inline function of libOtherLibrary.so (code details shouldn't matter):

template<class T>
inline void className<T>::clear() const
{
    Info << "Hello World!" << endl; // My message!
    if (isTmp() && ptr_)
    {
        if (ptr_->unique())
        {
            delete ptr_;
            ptr_ = 0;
        }
        else
        {
            ptr_->operator--();
            ptr_ = 0;
        }
    }
}

I then recompiled libOtherLibrary.so, followed by recompiling libMyLibrary.so. Finally I relinked (so no recompilation) exe.

The result was that any call to className<T>::clear() initiated in libMyLibrary.so used the old implementation of this inline method, whereas any call to className<T>::clear() initiated by libOtherLibrary.so used the new implementation.

When I then decided to also recompile exe (followed by linking it), the result was that the new implementation was always used.


My question is: Can someone explain to me why exe required recompilation, rather than relinking only?

That is, the inlining of the function className<T>::clear() of libOtherLibrary.so should occur during the compilation stage of libMyLibrary.so, doesn't it? After all, it is a function contained in libMyLibrary.so whom calls className<T>::clear(). Then I'd expect that linking exe is sufficient, as exe does not call this particular inline function. The linker alone will take care of any changed ABI compatability.


Solution

  • My question is: Can someone explain to me why exe required recompilation, rather than relinking only?

    Because, for your specific use-case, without it, you will inccur the wrath of ODR violation.


    The result was that any call to className<T>::clear() initiated in libMyLibrary.so used the old implementation of this inline method, whereas any call to className<T>::clear() initiated by libOtherLibrary.so used the new implementation.

    When you have a function-template say:

    template<class T>
    inline void className<T>::clear(){
        ....
    }
    

    And it is ODR used in multiple translation units (.cpp file). It's instantiation will be defined in each one of such translation unit because function-templates are implicitly inline.

    The rules for such multiple definition are stated here basic.def.odr/6. And one of the listed requirements states that "each definition of D shall consist of the same sequence of tokens;".

    Modifying that function template and recompiling some translation units making ODR use of it, and linking your program, without recompiling all the translation units making ODR-use of it violates the holy One Definition Rule of C++.

    Compiler toolchains are not required to diagnose it.