SourceXR

C/C++ Cross-Reference Tool

Object Factories in a Static Library

In this article we use the abstract factory pattern with concrete factories which self-register in a static library. Due to the way the linker is working we have to specify additional options to correctly link the program.

Here are the sample classes that we use to describe the issue.

Abstract Factory

The Object class is the abstract base class. Concrete classes will be instantiated by the concrete factories.

The interface consists of the two functions registerFactory() and createObject().

// file: fac.h

#include <string>

class Object {

public:
    virtual ~Object () { }

};

class BaseFactory {

public:
    virtual ~BaseFactory () { }
    virtual Object *create () = 0;

};

void registerFactory (const std::string &type, BaseFactory &);
Object *createObject (const std::string &type);

The two functions are implemented in the following file:

// file: fac.cpp

#include "fac.h"

#include <map>

typedef std::map<std::string, BaseFactory *> Factories;

Factories &factories () {
    static Factories _factories;
    return _factories;
}

void registerFactory (const std::string &type, BaseFactory &f) {
    factories ().insert (std::make_pair (type, &f));
}

Object *createObject (const std::string &type) {
    Factories::const_iterator it = factories ().find (type);
    if (it != factories ().end ()) {
        return it->second->create ();
    }
    return NULL;
}

The factory repository is static but withing the factories() function, so we avoid any static variable construction order issue.

Concrete Factory

This concrete factory merely builds a specific type of object. In a real program there would be many factories like this one:

// file: type1.cpp

#include "fac.h"
#include "object.h"

namespace {

    class Type1Object : public Object { };

    class ConcreteFactory : public BaseFactory {

    public:
        ConcreteFactory () {
            registerFactory ("type1", *this);
        }

        // covariant
        virtual Type1Object *create () {
            Type1Object *obj = new Type1Object;
            return obj;
        }

    };

    ConcreteFactory f;

}

As you can see the factory is declared in an anonymous namespace and has therefore internal linkage. As a peculiarity, we compile this concrete factory in a separate static library, with the following command lines:

g++ -c -o type1.o type1.cpp
ar r libtype1.a type1.o

Sample Program

The sample program creates a specific object:

// file: main.cpp

#include "fac.h"
#include <iostream>

int main () {

    Object *o = createObject ("type1");

    if (!o) {
        std::cerr << "failed to create object\n";
    }

    // use object
    std::cout << "creation ok\n";

    return 0;
}

We link against the type1 static library to get the correct factory. To compile it, we use the following command line:

g++ -o main facmain.cpp fac.cpp -L. -ltype1

However, if we execute this program we get the following output:

$ ./main
failed to create object

Due to linker behavior, symbols not used outside the library (the concrete factory is a static symbol (anonymous namespace)) will not be included in the final executable and therefore the factory is not registered.

To fix this, and force the inclusion of all symbols, we have to tell the linker to use all available symbols in the archive. This is done with the help of the --whole-archive option. The command line becomes:

g++ -o main main.cpp fac.cpp -L. -Wl,--whole-archive -ltype1 -Wl,--no-whole-archive

We reset the linker behavior to its default mode after the factory library.

And the execution now succeeds:

$ ./main
creation ok

Comments !