2

I have created a templated class where I would like to use the factory method. The templates should be explicitly defined to create objects of type int, double and bool.

The factory has a checkIn registry mechanism through the use of the a static bool.

The factory is defined as:

#ifndef Factory_H
#define Factory_H

#include <memory> 
#include <map>

template<class Type> 
class Base;

template<class Type> 
class Factory
{
    public:
        using createObj = std::shared_ptr<Base<Type>>(*)();

        static bool registerObj( const std::string& name, createObj type)
        {
            std::map< std::string, Factory::createObj >& registry = getRegistry();

            if(registry.find(name) == registry.end())
            { 
                registry[name] = type;
                return true;
            }
            return false;
        };

        static std::shared_ptr<Base<Type>> New( const std::string& name)
        {
            auto it = getRegistry().find(name);
            if (it == getRegistry().end()) {
                return nullptr;
            }
            return it->second();
        };
        
    private:
        static std::map<std::string, createObj>& getRegistry()
        {
            static std::map<std::string, Factory::createObj> registry;
            return registry;
        };        
};

#endif

The base class is defined as:

#ifndef Base_H
#define Base_H

#include "factory.h"

template<class Type>
class Base: public Factory<Type>
{
    public:
        Base();
        virtual void foo() = 0;
};

#endif

and implemented as:

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

template <class Type>
Base<Type>::Base()
{}

// Explicit initalization
template class Base<int>;
template class Base<double>;
template class Base<bool>;

The derived class is defined as:

#ifndef Derived_H
#define Derived_H

#include "Base.h"

template<class Type>
class Derived: public Base<Type>
{
    private:
        static bool checkIn_;
        static std::string className_;

    public:
        Derived();
        virtual void foo() ;
        static std::shared_ptr<Base<Type>> Create();

};
#endif

and implemented as:

#include "Derived.h"

template<class Type>
Derived<Type>::Derived()
{}

template<class Type>
std::string Derived<Type>::className_("Derived");

template<class Type>
std::shared_ptr<Base<Type>> Derived<Type>::Create()
{
    return std::make_shared<Derived>();
}


template<class Type>
bool Derived<Type>::checkIn_ = Base<Type>::registerObj(Derived::className_, Derived::Create);

template<class Type>
void Derived<Type>::foo()
{
    std::cout << typeid(Type).name() << std::endl;
}

// Explicit initalization
template class Derived<int>;
template class Derived<double>;
template class Derived<bool>;

The main.cpp is defined as:

#include<iostream>
#include "Derived.h"
#include "Base.h"

int main()
{
    auto obj1 = Base<int>::New("Derived");
    auto obj2 = Base<double>::New("Derived");
    auto obj3 = Base<bool>::New("Derived");

    obj1->foo();
    obj2->foo();
    obj3->foo();

    return 0;
}

@ Edited

If I compile everything in one executable: g++ -g *.cpp -o main. The factory method works.

If I try to compile it as a library; g++ -g -fPIC Base.cpp Derived.cpp -shared -o test.so followed by g++ -g -o main main.cpp -I . -L. test.so it no longer works, I get a segmentation fault. There is nothing in the registry... I would guess the static bool is not doing its job.

How can I make this work?

In the scope of the project, I would like to have a library that is able to use the factory method for int, double and bool that all share the same interface. They will differ in a std::vector<Type> attribute which will store some data. In case the factory method is not the best design which alternatives are best?

Best Regards

R. Smith
  • 103
  • 7
  • 1
    *it no longer works* How do you know it? Do you get errors? Which errors? – 273K Apr 23 '23 at 20:20
  • BTW explicitly instantiated templates are not used in .so, thus are not exported by a linker. – 273K Apr 23 '23 at 20:22
  • 1
    This is not a duplicate of [Why can templates only be implemented in the header file?](https://stackoverflow.com/q/495021/7976805), OP uses explicit instantiations. Altough I agree that the question is missing description of that exactly is wrong to make it [mcve]. – Yksisarvinen Apr 23 '23 at 20:31
  • Hello to all, and thanks for the input. I think the provided code in the question should be a minimal reproducible example. In fact even the compilation flags to reproduce the problem have been given. @273K The code gives a `segmentation fault` because there is no class registered [this is problem!]. In the scope of my code, the factory method should be used for the indicated types `int` `double` and `bool`. Instead of creating 3 classes with 3 factory methods I thought I could make it a templated class and let the compiler do the rest with the explicit instanciation. – R. Smith Apr 24 '23 at 07:55

1 Answers1

1
 ldd main
        linux-vdso.so.1 (0x00007ffeec319000)
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fefe98c2000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fefe98a2000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fefe967a000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fefe9593000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fefe9b18000)

Your executable doesn't even know test.so exists, it doesn't reference anything from there so the shared library is simply ignored. No wonder registerObj is not called.

Compile with

g++ -g -o main main.cpp -I . -L. -Wl,--no-as-needed test.so -Wl,--as-needed -Wl,-rpath=.

Another way to achieve the same goal is moving the definition of Factory::New to a new Factory.cpp file, and explicitly instantiating with only the types needed. This way main references symbols defined in test.so and no special linker flags are needed. This also makes an attempt to call say Base<char>::New a link-time error, rather than a run-time error.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • This worked! Even without the `static_cast(&Derived::checkIn_);`. So it was a linking error... Would you be so kind as to explain the flags? The `ldd` comamnd is very welcome! If I comment out one of the available types, say `bool`. It still allows the construction and compilation [altough this will produce a `segmentation fault`]. Anyway to give an error if a type other than the explicit template types is used? – R. Smith Apr 24 '23 at 09:18
  • 1
    `man ld` should provide info on ld flags. If you want to error out on invalid types, mobe `Factory::New` definition to a .cpp file and explicitly instantiate only those types you need --- and then you won't need special ld flags. – n. m. could be an AI Apr 24 '23 at 09:21
  • This solved everything! Thanks! – R. Smith Apr 24 '23 at 09:31
  • Just for completeness can you tell how it can be compiled after putting the `New` method in a `.cpp` file. I did that but still can only link the library with the commands you provided: `g++ -g -o main main.cpp -I . -L. -Wl,--no-as-needed test.so -Wl,--as-needed -Wl,-rpath=.` which flags can be dropped ? – R. Smith Apr 24 '23 at 09:38
  • Works for me. You need to explicitly instantiate `New` (using `template ...` and not `template <> ...` syntax). Please ask a separate question if there is any problem with it. – n. m. could be an AI Apr 24 '23 at 09:55
  • Oh... "I was doing `template class Factory;` `template class Factory;` – R. Smith Apr 24 '23 at 10:01
  • I will post a follow up question. – R. Smith Apr 24 '23 at 10:15
  • You can use the same link command you used originally – n. m. could be an AI Apr 24 '23 at 10:16
  • I have place a follow up question here: https://stackoverflow.com/questions/76090929/factory-method-in-dynamic-library-with-explicitly-instantiated-templated-classes. Would you be so kind as to check if the implementation of `Factory.cpp` is as you described? – R. Smith Apr 24 '23 at 10:30