Avoiding Static Linking Collisions in C++ with Dynamic Namespacing

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 <libFoo.h>

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

/cpp-dynamic-namespacing/blob/master/libFoo_v1/libFoo.h
1
2
3
4
5
6
7
8
9
10
11
12
#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

/cpp-dynamic-namespacing/blob/master/ColonelA.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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 #defines FooNamespace to be ColonelATerritory prior to including the header file

/cpp-dynamic-namespacing/blob/master/ColonelA.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "ColonelA.h"
#include <stdio.h>
#define FooNamespace ColonelATerritory#include <libFoo.h> 
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

/cpp-dynamic-namespacing/blob/master/libFoo_v2/libFoo.h
1
2
3
4
5
6
7
8
9
10
11
12
#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

/cpp-dynamic-namespacing/blob/master/ColonelB.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef COLONEL_B#define COLONEL_Bnamespace 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

/cpp-dynamic-namespacing/blob/master/ColonelB.cpp
1
2
3
4
5
6
7
#include "ColonelB.h"
#include <stdio.h>
#define FooNamespace ColonelBTerritory#include <libFoo.h>
 
using namespace ColonelBTerritory;
...

Finally, we create a main() to run our example, in order to demonstrate that all works as is expected.

/cpp-dynamic-namespacing/blob/master/TheGeneral.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#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 Foos assigned to the different Colonels 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