There are many development libraries that provide inline functions (or macros) as part of their public API. This is often done for efficiency reasons, although some times it's done because developers don't know the consequences of doing such things (this last thing is just a guess, but it can perfectly happen). Providing such inlined functions breaks the whole idea of encapsulation and shared libraries. Let's see why.

Consider the following simple class:

/* In foo.h. */
class foo {
int m_value;

public:
int get_value(void) { return m_value; }
void set_value(int v);
/* ... */
};

/* In foo.c; this is _not_ inlined. */
void
foo::set_value(int v)
{
m_value = v;
}

Now imagine that this class belongs to a shared library, say libbar.so.1.0. Given this, our Joe user does this in his code, which is perfectly legal:

foo a;

a.set_value(5);
/* ... do whatever with 'a' ... */
int b = a.get_value();

When this code is compiled, the compiler replaces the call to foo::get_value() with the method's code, avoiding a function call, a return and all the stack set up; all the action takes place in the user's code, not in the library. Typically, getting a value from a structure means reading a concrete position of memory within it, described by its offset from the beginning. OTOH, the call to foo::set_value() is correctly made into a regular function call inside the shared library's text.

Some time later, the libbar developers decide to change the internal representation of the foo class for whatever reason. According to the encapsulation principle used in object oriented designs, they should be able to, after all. Let's suppose they add a new integer before the m_value field, called m_id. Unwillingly, the developers have just changed the ABI of their library and, if they don't take care to update the library's major number, seriuos problems will arise. But, why?

Our Joe user again sees a new release of libbar, say 1.1, so he rebuilds and updates it in his machine, replacing libbar.so.1.0 with libbar.so.1.1; these two libraries typically share the same soname, libbar.so.1, because they are compatible in theory. According to how shared libraries work, he oughtn't rebuild his application.

The set_value() call will continue to work correctly because the application will call the new function in the updated shared library. However, the execution of get_value() will be broken; oops! Remember the sample code shown above? It was compiled as an offset within the class, which is now different! This getter will return an incorrect value, no matter what he does. He'll be forced to rebuild his application to adjust to the new ABI.

Conclusion: be very careful when defining inlined methods and macros. If you need to fix a mistake or modify the internal representation of your code in the future, you will be unable to. Personally, I avoid inlined code in all public interfaces, despite this introduces a small performance degradation; however, they are perfectly fine for internal code.

It's a pity that careless C++ developers make so intensive use of such inlined code. BTW, note that although this has focused on C++, the same is true for, e.g., C99, which provides an inline keyword.

Edit (Oct 3rd): Based on this reply, I've removed some (really minor) references to templated code from the article; they certainly didn't belong here.

Subscribe via RSS · Go to posts index

   Delivered by FeedBurner

Comments from the original Blogger-hosted post: