Private Practice: What’s in a Proxy-Style?

A new session in the private practice, “What’s in a Proxy Style?”, examines QProxyStyle and derives some important caveat when applying a classical Design Pattern.

This is the original-language version of my recent Mythos Proxy-Style article.

Translated: Pimp My Pimpl (part 2)

I’m happy to report that I finally sat down to translate the second part of my Heise Developer article on the Pimpl Idiom to English. Please find it here.

Translated: Pimp My Pimpl (part 1)

I’m happy to report that I finally sat down to translate the first part of my Heise Developer article on the Pimpl Idiom to English. Please find it here.

Fun with exceptions


Here’s a guideline for you: If your library uses exceptions, and you intend users of your library to catch them, don’t implement them inline in the header.

Example:

class LIB_EXPORT MyException : public std::exception {
public:
    explicit MyException( ... );
    // ... (but no dtor)
};

Here, the compiler will generate a MyException destructor for you, since you didn’t provide one. Compiler-synthesised destructors are public, inline, have an empty body, and the same exception specification as the base class’ destructor. So, the above is equivalent to:

class LIB_EXPORT MyException : public std::exception {
public:
    explicit MyException( ... );
    ~MyException() throw() {}
    // ...
};

Unfortunately (well, actually fortunately, but not for our scenario), the destructor of std::exception is virtual, so ~MyException() throw() {} (explicitly written by you or synthesised by the compiler) is the definition (as opposed to declaration) of the first virtual function of the class MyException. Most C++ compilers take that as a cue to emit the MyException virtual function table, as well as its RTTI information at that point.

Oops. Does that mean we’ll end up with duplicate vtables and RTTI information for a class whose first virtual function is defined inline?

Yes, it does. That’s not normally a big problem, since the linker will merge them at link time. However, at least my GCC 4.3 doesn’t do this across shared library boundaries.

Thus, we end up with a situation where the throw site uses a different vtable (and std::type_info instance) for the exception class than the user of the library (which has its own copy helpfully provided by the compiler). The result is that you can’t catch the exception in client code anymore:

// in the client
try {
    doSomethingThatThrowsMyException();
} catch ( const MyException & e ) { // never hit
    log( "Caught MyException: %s", e.what() );
    return;
} catch ( const std::exception & e ) { // hit instead
    log( "Caught unexpected exception %s: %s:", typeid(e).name(), e.what() );
    return;
}

Now, consider the surprise—the sheer terror—of a developer using your library, only to find he’s thrown a MyException that he can’t catch.

Don’t go there. Define your exception classes out-of-line!

[Update 2010-12-02: The problem only occurs if you throw the exception from out-of-line code. If you only throw the exception from inline code, everything is peachy.]

BC/SC Gotcha: reimplementing a virtual function


This might surprise you: Adding a reimplementation of a base class virtual function to a derived class might be binary incompatible. Here’s why.

Assume you have the following classes in a library that maintains binary compatibility across releases:

// release 1.0
struct base { virtual f() {} };
struct derived : base {}; // doesn't reimplement base::f()

And in an application using the above library:

derived d;
d.f(); // ok, calls base::f()

Q: Is d.f() a virtual function call?
A: No. Not if your compiler is any good.

When the compiler can prove the dynamic type of an object, it will resolve the polymorphic calls at compile time. In other words: It will de-virtualise the call.

In our case, the compiler can prove that d‘s dynamic type is derived, so it just inserts the function call to base::f(). Don’t believe me? Ask your compiler:

struct base {
    virtual ~base();
    virtual void f() const;
};

struct derived : base {
    //void f() const;
};

void foo1( derived d ) {
    d.f();
}

void foo2( const derived & d ) {
    d.f();
}

My GCC gives me (with -O) these assembly listings for foo1() and foo2(), resp.:

foo1() foo2()

_Z4foo17derived:
.LFB2:
subq $8, %rsp
.LCFI1:
call _ZNK4base1fEv
addq $8, %rsp
ret

_Z4foo2RK7derived:
.LFB3:
subq $8, %rsp
.LCFI0:
movq (%rdi), %rax
call *16(%rax)
addq $8, %rsp
ret

Even if you don’t speak AMD64 assembler, you will recognise that foo1() calls base::f() directly while foo2() calls indirectly through base‘s virtual function table.

Now, assume we’re adding a reimplementation of base::f() to derived:

// release 1.1
struct derived : base {
    /* reimp */ void f() {}
};

But we won’t recompile the application. What happens is that the application still calls base::f(). Yes, even across DSOs. The linker will replace the direct function call with an indirect one into the DSO, for sure. But it won’t second-guess the compiler and insert a virtual function table lookup again.

So now that the mechanism is clear (hopefully): Is this a binary compatibility issue?

That depends entirely on what the new functions do. If they (even ever-so-slightly) reinterpret the meaning of data in the class, class invariants might be in jeopardy.

Consider this: Code in the application that can prove the dynamic type of a base instance is a derived will not call the new reimplementations. Code that cannot, will. An instance that will be manipulated with one set of functions on one hand, and another on the other might have a hard time maintaining its class invariants if one set of functions expects one set of invariants, and the other another.

Note how this is similar to changing functions with inline linkage: Depending on whether the compiler decides to inline the code at one particular call site or not, the old copy or the new copy of the code will be called. The difference is one of awareness: If you change a method with inline linkage, you are usually aware of the possible problem. When adding a reimplementation of a virtual function, you’re probably not. Up to now you weren’t, that is :).

To summarise: Adding a reimplementation of a virtual function to a class can lead to clients of the class either calling or not calling the new implementation, depending on whether the compiler can prove at compile time that static and dynamic type of an instance are the same. For the same instance, this will typically succeed in one function and fail in another. Problems arise when the reimplementation does not take into account that it might be bypassed.