0

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:

  1. why does the archive version fail?
  2. how can I generate an archive file that will appear the same as an object file from the linkers perspective?
doc07b5
  • 600
  • 1
  • 7
  • 18

1 Answers1

1

The problem isn't the archiver, it's the linker. The linker takes every object file, plus what's needed from archives. The members of your archive do not resolve any unresolved references, and are therefore not needed.

The gnu linker understands --whole-archive, which is what you intended here.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • In darwin (mac osx), the system linker does not have "--whole-archive". Instead there is an "-all_load" command that seems to work. Thanks! – doc07b5 Jun 22 '16 at 15:11