I use a rather involved approach to object allocation using a "catalog" approach. The code generates a catalog object (global static map) which is populated by base pointers, which point to derived objects. This is all blasphemous, but not the point here. Upon program execution, the catalog is filled by all derived objects, and we can allocated anything that was registered with the catalog, without having to maintain a list of candidate derived classes. Great. However, when one of the derived classes is run though the archiver (ar), and the archive is used in place of the object file for the linker, the code fails. Can someone tell me why this is happening? Example Code:
//Base.h
#include<unordered_map>
#include<string>
#include<map>
#include<iostream>
#include<memory>
class Base
{
public:
Base( std::string name ):m_name(name){}
virtual ~Base() {}
std::string m_name;
};
class ObjectCatalogueEntryBase
{
public:
typedef std::map< std::string, ObjectCatalogueEntryBase* > CatalogueType;
ObjectCatalogueEntryBase(){}
virtual std::unique_ptr<Base> Allocate( std::string const & ) = 0;
virtual ~ObjectCatalogueEntryBase(){}
static CatalogueType& GetCatalogue()
{
static CatalogueType catalogue;
return catalogue;
}
static std::unique_ptr<Base> Factory( const std::string& objectTypeName, std::string const & name )
{
std::cout<<"Creating solver of type: "<<objectTypeName<<" name "<<name<<std::endl;
ObjectCatalogueEntryBase* const entry = GetCatalogue().at(objectTypeName);
return entry->Allocate(name);
}
};
template< typename TYPE >
class ObjectCatalogueEntry : public ObjectCatalogueEntryBase
{
public:
ObjectCatalogueEntry():
ObjectCatalogueEntryBase()
{
std::string name = TYPE::CatalogueName();
(ObjectCatalogueEntryBase::GetCatalogue())[name] = this;
std::cout<<"Registered Solver: "<<name<<std::endl;
}
~ObjectCatalogueEntry() final{}
virtual std::unique_ptr<Base> Allocate( std::string const & name) final
{
return std::unique_ptr<Base>(new TYPE(name));
}
};
/// Compiler directive to simplify autoregistration
#define REGISTER_FACTORY( ClassName) namespace{ ObjectCatalogueEntry<ClassName> reg_; }
next file:
// Derived.h
#include "Base.h"
class Derived : public Base
{
public:
Derived( std::string name );
~Derived();
static std::string CatalogueName() {return "Derived";}
};
next file:
// Derived.cpp
#include "Derived.h"
Derived::Derived( std::string name):Base(name)
{}
Derived::~Derived()
{}
REGISTER_FACTORY(Derived)
next file:
// main.cpp
#include "Derived.h"
int main()
{
std::string newName("Foo");
auto solver = ObjectCatalogueEntryBase::Factory(Derived::CatalogueName(),newName);
return 0;
}
And the Makefile:
CPP=g++-mp-6
test: main.o Derived.o
${CPP} -std=c++14 -o test main.o Derived.o
testlib: main.o Derived.a
${CPP} -std=c++14 -o testlib main.o Derived.a
main.o: main.cpp Base.h Derived.h
${CPP} -std=c++14 -c main.cpp
Derived.o: Derived.cpp Derived.h
${CPP} -std=c++14 -c Derived.cpp
Derived.a:
ar qcsv Derived.a Derived.o
clean:
rm *.o *.a test testlib
all: test testily
So two executables are linked. The first one (test) is linked with object files and produces the "correct" result:
$ ./test
Registered Solver: Derived
Creating solver of type: Derived name Foo
The second one (testlib) is linked with the Derived.o file replaced with Derived.a, which was created with "ar" using only Derived.o. The result is:
./testlib
Creating solver of type: Derived name Foo
terminate called after throwing an instance of 'std::out_of_range'
what(): map::at
Abort trap: 6
Obviously the registration did not occur here, and the map is empty. Same result with gcc6, and apple clang7. I suspect it has something to do with the global static map, but don't understand "ar" enough to know what it is stripping from the object file. So there are two questions:
- why does the archive version fail?
- how can I generate an archive file that will appear the same as an object file from the linkers perspective?