Sharing code is not a trivial matter. How are you going to share the code? Will you share it via source code from sub-repositories and have the consuming project build from scratch? Will you compile it into dynamic libraries and share the interfaces to consumers? What if you need to change the interfaces after you have released?
In order to maintain flexibility with API changes, it is very tempting to just share a body of code via source code, but there are some caveats that must be taken into account. In many cases, you should be fine. But if you have multiple things in your system that are building from source code and/or static linking the code into their own libraries, then you may be in trouble.
What is Static Linking?
Static linking means that a body of code gets built, and then a consuming library or application inserts the binary form of the code into its own binary package. Therefore, if the library changes down the road, you still have the copy you originally built and linked to, and the interface has been solidified.
What is Dynamic Linking?
Dynamic linking means that the code you are using has been pre-compiled into a dynamic library. The dynamic library provides a table at runtime to the target system that allows the binding to the implementation of the exposed things at load time.
Of course, this mechanism can cause issues if the shared library is allowed to be changed at some point in the future. If the API that was given to you at the time of building against this library changes, you have the danger of the not being able to attach the things you originally built against, and this will result in a load-time error when your consuming library or application starts up.
What about Rebuilding Shared Code?
Should I just rebuild the shared code for each build of my consuming library or application? You can. This is one way that developers try to avoid the burden of maintaining binary compatibility. But…
The main concern here is process boundaries. If you have more than one library or application that has static linked the shared code into their own binaries, you may think you are safe. But when the libraries run within the same process, you may run into some very strange things if and when the shared code changes.
Although you may think you are safe having multiple copies of the pre-compiled/static-linked code in your own little corner, things are shared within the same process, if the system thinks they are the same thing.
Suppose you have a class Foo
that is shared into multiple things (e.g. ThingA
and ThingB
) in the same process. When the different pieces are loaded, the C++ runtime will give you some help in order to minimize resource usage. If ThingA
loads Foo
, and then ThingB
loads Foo, the system will notice you are trying to load the same object, and it will graciously give you the one it already loaded earlier. If the source code it was built from has caused the API or behavior to change… BOOM!
Essentially, both classes have the same namespace. Namespaces are used to distinguish/clarify the particular instance that should be loaded for a given object. Therefore, lets use them to our advantage!
A Solution – Dynamic Namespacing to the Rescue!
Although you are technically creating an instance of the same class, you can utilize namespacing to force use of the proper versions of Foo
at runtime. ClassA
would be wrapped in its namespace, and ClassB
would likewise be wrapped in its namespace.
As long as the namespaces differ, then the conflict is gone! As far as the system is concerned, you now have 2 fully unique classes — one called A:Foo
and another called B:Foo
.
Rather than manually changing namespaces for each specific version of Foo
, you can use the preprocessor to do the work for you!
In the example below, we use a common namespace for the class consuming Foo
and we redefine the namespace of the Foo
we are compiling/using using good ole’ #define
.
Though Foo
defines a namespace FooNamespace
, we simply redefine it prior to including libFoo.h
#define FooNamespace SomeCustomNamespace
#include
Alternatively, FooNamespace
could be redefined from the command line when compiled
> g++ -DFooNamespace=SomeCustomNamespace -c ColonelA.cpp -o obj/ColonelA.o -I"./libfoo_v1/" -I"." -Wall -std=c++11
A Working Example:
Suppose we create a library libFoo
containing class Foo
, and wrap the class in a namespace FooNamespace
#ifndef LIB_FOO
#define LIB_FOO
namespace FooNamespace
{
static const char* _My40 = "Colonel sir! I am 'Original', sir!";
class Foo
{
public:
const char* Report40() {return _My40;}
};
}
#endif
We then create a consumer ColonelA
for Foo
inside a namespace ColonelATerritory
#ifndef COLONEL_A
#define COLONEL_A
namespace ColonelATerritory
{
class Foo;
class ColonelA
{
public:
ColonelA();
~ColonelA();
void Report();
private:
Foo* _Foo;
};
}
#endif
The implementation of ColonelA
#define
s FooNamespace
to be ColonelATerritory
prior to including the header file
#include "ColonelA.h"
#include
#define FooNamespace ColonelATerritory
#include
using namespace ColonelATerritory;
ColonelA::ColonelA()
{
_Foo = new Foo();
printf("ColonelA reporting for duty, sir!\n");
}
ColonelA::~ColonelA()
{
delete _Foo;
_Foo = NULL;
}
void ColonelA::Report()
{
printf("ColonelA asks its Foo: 'What's your 40 Foo?'\n");
printf("ColonelA's Foo: %s\n", _Foo->Report40());
}
Then, suppose Foo
is modified to report a different message
#ifndef LIB_FOO
#define LIB_FOO
namespace FooNamespace
{
static const char* _My40 = "Colonel sir! I am 'Extra Crispy', sir!";
class Foo
{
public:
const char* Report40() {return _My40;}
};
}
#endif
Then, suppose we add ColonelB
, that will use Foo
as well
#ifndef COLONEL_B
#define COLONEL_B
namespace ColonelBTerritory
{
class Foo;
class ColonelB
{
public:
ColonelB();
~ColonelB();
void Report();
private:
Foo* _Foo;
};
}
#endif
But, in the implementation for ColonelB
, we assign FooNamespace
to ColonelBTerritory
and use the latter as the new Colonel’s namespace.
If you refer to the project Makefile, you will see that we force ColonelB
to use the new Foo
.
NOTE: Using a unique namespace for Foo
(e.g. ColonelBTerritory
) is critical to ensure there are no collisions when the classes are loaded/linked at runtime
#include "ColonelB.h"
#include
#define FooNamespace ColonelBTerritory
#include
using namespace ColonelBTerritory;
...
Finally, we create a main()
to run our example, in order to demonstrate that all works as is expected.
#include
#include "ColonelA.h"
#include "ColonelB.h"
int main(void)
{
printf("The General: All Colonels report their 40! STAT!\n");
ColonelATerritory::ColonelA a;
ColonelBTerritory::ColonelB b;
a.Report();
b.Report();
printf("The General: All Colonels and their Foos are reporting for duty!\n");
}
The output of building and running the full example shows that all is well, and the Foo
s assigned to the different Colonel
s have resolved properly:
> make all rm -rf obj/TheGeneral.o obj/ColonelA.o obj/ColonelB.o obj general mkdir -p obj g++ -I"./libfoo_v1/" -I"." -Wall -std=c++11 -c ColonelA.cpp -o obj/ColonelA.o g++ -I"./libfoo_v2/" -I"." -Wall -std=c++11 -c ColonelB.cpp -o obj/ColonelB.o g++ -I"." -Wall -std=c++11 -c TheGeneral.cpp -o obj/TheGeneral.o g++ -Wall obj/TheGeneral.o obj/ColonelA.o obj/ColonelB.o -o general Running Example: ./general The General: All Colonels report their 40! STAT! ColonelA reporting for duty, sir! ColonelB reporting for duty, sir! ColonelA asks its Foo: 'What's your 40 Foo?' ColonelA's Foo: Colonel sir! I am 'Original', sir! ColonelB asks its Foo: 'What's your 40 Foo?' ColonelB's Foo: Colonel sir! I am 'Extra Crispy', sir! The General: All Colonels and their Foos are reporting for duty!
The full source for this example is available on GitHub at:http://github.com/barneywilliams/cpp-dynamic-namespacing