Sunday, August 28, 2016

Battling With C++'s Design?

Today I bring you a simple problem.  One that has persisted since the humble beginnings of C++.  It's about design and class layouts.

Consider an object A.  We must hide every piece of data of object A from the rest of the program.  Ie, if A uses OpenGL, then the GL headers and GLuint are really implementation details that should not be implicitly included by whoever uses our class A.

The typical solution is to create an interface B with pure virtual functions, make A inherit from B, and a static create method in B that instantiates A.  In this case, B is what's found in the header file, A is defined inline within the source file.  Now I could create something called A2 that uses Metal and everyone would be happy.

Let's put our code in a dynamic library.  Now let us suppose that we add another method to A, hence another virtual function to B.  Recompile the library.  Now we two versions of the library, do we recompile every library/application that depends on B, or give it a version number?

If we give the library a version number, we are essentially creating DLL hell, where there are a ton of DLL's with varying versions.  Recompiling everything isn't always an option, consider other people using the old interface en masse...

We could create an interface Bv2 deriving from B, which then A derives from Bv2.  Old code works with B, new code with Bv2.  Imagine someone with a large code base using B extensively renaming it to Bv2.  Again, it's not ideal.

The root of the problem with the interface, I believe, is that the methods are also part of the data of object B.  B may be masking A, but it is still exposing its v-table.  Hence why the interface once released gets set in stone.  Does this have to be the case though?

We can add new symbols to a dynamic library, just not change existing ones.

C provides an answer - forward declare a pointer to an object, then make global methods that use it.  Add more methods as needed, old applications will still work as long as the signatures don't change.  Furthermore, the implementation details are completely masked within the header.  All of this at the cost of autocomplete after typing ->.

Does it?  By going the C style route we lose the v-table unless our simple C functions forward to a C++ call behind the scenes.  I'm not a fan of using enumerations to determine type as that leads to a mess of conditionals.

Regarding the specific example of two rendering APIs, we simply want always one or always the other. Function pointers that are initialized on startup would do the trick.  A manual v-table without the per object overhead.

All this rambling to say that, sometimes, a C like interface is the better choice than an object oriented interface.  Newer isn't always better, even with technology.


No comments:

Post a Comment