1

I implemented a factory class based on the following article available here.

I have one problem, however, and I think it’s related to compiler optimisations.

I have a hierarchy of classes where class Node (in Node.h/Node.cpp) is the base class and, for example, BuildingLot (in BuildingLot.h/BuildingLot.cpp) is a subclass.

In both of the source files, declared as static I have a Registrar class.

Node.cpp:

static Registrar<Node> _registrar(“Node”);

BuildingLot.cpp:

static Registrar<BuildingLot> _registrar(“BuildingLot”);

If I try to use the Factory, it works perfectly for Node class. I, however, have to first create an instance of BuildingLot for it to be registered in the factory.

In a unit test I have:

NodePtr node = Factory::Instance()->Create(“Node”);
NodePtr bl = Factory::Instance()->Create(“BuildingLot”);

bl is always nullptr. The Registrar constructor is never executed and, therefore, the Factory class never knows about the BuildingLot class. If I do this:

BuildingLot b;
NodePtr node = Factory::Instance()->Create(“Node”);
NodePtr bl = Factory::Instance()->Create(“BuildingLot”);

Then all works. The Registrar is called, and the Factory creates a BuildingLot.

I think, since the BuildingLot isn’t used in the first example, the compiler optimised and didn’t even compile the BuildingLot.cpp.

Is this possible? How can I solve this?

Edit: The Registrar class is, in fact, a template class. I just forgot to insert the template parameters.

Here's my code as simple as I could make it. I hope I didn't leave anything out. If I uncomment the first line in main(), then it all works.

NodeFactory.h:

class NodeFactory{
private:
    /// Map of factory functions
    std::map<std::string, std::function<std::shared_ptr<Node>(void)>> mFactoryFunctions;
public:
     /// Get Singleton
    static NodeFactory* Instance();

    /// Register Function.
    void Register(const std::string &name, std::function<std::shared_ptr<Node>(void)> factoryFunction);

    /// Factory Function.
    std::shared_ptr<Node> Create(const std::string &name);
};

NodeFactory.cpp:

/**
 * Get Singleton
 */
NodeFactory* NodeFactory::Instance(){
    static NodeFactory factory;
    return &factory;
}

void NodeFactory::Register(const std::string &name, std::function<std::shared_ptr<Node>(void)> factoryFunction){
    mFactoryFunctions[name] = factoryFunction;
}

std::shared_ptr<Node> NodeFactory::Create(const std::string &name){
    if(mFactoryFunctions.find(name) == mFactoryFunctions.end())
        return nullptr;

    std::shared_ptr<Node> n = mFactoryFunctions[name]();

    return n;
}

Registrar.h:

#define REGISTER_NODE_TYPE(NODE_TYPE) static NodeRegistrar<NODE_TYPE> _registrar(#NODE_TYPE);

template<class T>
class NodeRegistrar{
private:

public:

    NodeRegistrar(const std::string &name){
        NodeFactory::Instance()->Register(name,
                                          [](void) -> std::shared_ptr<T> { return std::make_shared<T>(); }
                                          );
    }
};

Node.h:

class Node{
private:        
    /// The node ID.
    NodeID mID;

public:
    /* ****************************
     * Construction & Destruction *
     * ***************************/
    Node();
    Node(const NodeID &nodeID);
    virtual ~Node();
};

Node.cpp:

REGISTER_NODE_TYPE(Node);

Node::Node(){
    mID = -1;
}
Node::Node(const NodeID &nodeID){
    mID = nodeID;
}

Node::~Node(){

}

BuildingLot.h:

class BuildingLot : public Node{
public:

    BuildingLot();
    BuildingLot(const NodeID &nodeID);
    virtual ~BuildingLot();

};

BuildingLot.cpp:

REGISTER_NODE_TYPE(BuildingLot);

BuildingLot::BuildingLot(){

}

BuildingLot::BuildingLot(const NodeID &nodeID):Node(nodeID){

}

BuildingLot::~BuildingLot(){

}

main.cpp:

int main(int argc, const char * argv[]){   

// BuildingLot bl; // if I uncomment this, then it works
std::shared_ptr<Node> node = NodeFactory::Instance()->Create("Node");
std::shared_ptr<Node> buildingLot = NodeFactory::Instance()->Create("BuildingLot");

if(node == nullptr)
    std::cout << "node is nullptr" << std::endl;
if(buildingLot == nullptr)
    std::cout << "buildingLot is nullptr" << std::endl;

return 0;

}

zync
  • 463
  • 3
  • 17
  • What is your compiler ? If I recall correctly, it shouldn't be allowed to optimize away constructors with side effects... – Quentin Aug 29 '14 at 12:15
  • In that article, the Registrar class is a template. In your code snippets, it isn't. So how is it supposed to know which class to register? – Sebastian Aug 29 '14 at 12:20
  • 3
    Possible duplicate: http://stackoverflow.com/q/1300836 The problem is the static initialization order fiasco. – dyp Aug 29 '14 at 12:21
  • Please provide a http://stackoverflow.com/help/mcve – Sebastian Aug 29 '14 at 12:21
  • @dyp: After reading the article, I don't think so - it uses a function to return the map of the registrar. So the map should exist at the time it is accessed. – Sebastian Aug 29 '14 at 12:22
  • @Sebastian The problem in both cases is the registration at load-time through static initialization. The details of how the registration process itself works do not matter; only how it is (intended to be) started. – dyp Aug 29 '14 at 12:23
  • @dyp: But as soon as main() is started, it should be done, shouldn't it? That's why I ask for the mcve... Is bl a global variable, created in main() or a function called by main()? – Sebastian Aug 29 '14 at 12:25
  • 2
    @Sebastian It's described in the link the OP provides in the first line, and a(n unfortunately) rather common technique. [basic.start.init]/4 "It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of `main`. If the initialization is deferred to some point in time after the first statement of `main`, it shall occur before the first odr-use of any function or variable defined in the same translation unit as the variable to be initialized." – dyp Aug 29 '14 at 12:29
  • Watch out with that example. The base class has no virtual destructor. Also `MyFactory::CreateInstance()` has really weird implementation. – BЈовић Sep 09 '14 at 12:36
  • Static variables inside methods are initialised the first time the method is called and "remain" there forever. So an instance is automatically constructed when first needed and the same instance is always available in following calls. Already added a virtual destructor. Thanks for pointing that :) – zync Sep 09 '14 at 12:58

1 Answers1

1

In the end, I opted by manually registering the lambda creators in the factory class. Its very simple if a macro is created and used and the number of lines of code is exactly the same.

zync
  • 463
  • 3
  • 17