【问题标题】:Factory and singleton patterns: undefined reference工厂模式和单例模式:未定义的引用
【发布时间】:2017-04-05 13:33:10
【问题描述】:

我试图从本网站的其他问题中了解我的情况,但我还没有真正找到一个好的答案。我尝试了我发现的大多数建议,但仍然遇到相同的错误。

我正在尝试实现一个基于单例和 CRTP 的工厂。所以我有一个 Singleton 类,在 Singleton.h 中定义:

template<class T>
class Singleton
{
public:
  static T &instance()
  {
    static T one;
    return one;
  }

  Singleton(const Singleton &) = delete;
  Singleton(Singleton &&) = delete;
  Singleton &operator=(const Singleton &) = delete;
protected:
    Singleton() = default;
};

我还有一个 Factory 类,在 Factory.h 中定义和实现。工厂创建层次结构的对象,就本问题而言,其基类是Object。这些对象都有一个接受double的构造函数。

class Factory : public Singleton<Factory>
{
    friend class Singleton<Factory>; // to access constructor
public:
    using createFunction = Object *(*)(double);

    void registerObject(const std::string &, createFunction);
    Object *createObject(const std::string &, double) const;
private:
    Factory() = default;
    std::map<std::string, createFunction> theCreatorFunctions;
};

void Factory::registerObject(
    const std::string &ObjectId,
    createFunction creatorFunction)
{
    theCreatorFunctions.insert(
        std::pair<std::string, createFunction>(
            ObjectId, creatorFunction));    
}

Object *Factory::createObject(
    const std::string &ObjectId, double a) const
{
    auto it = theCreatorFunctions.find(ObjectId);
    if (it == theCreatorFunctions.end())
    {
        std::cout << ObjectId << " is an unknown object."
                  << std::endl;
        return nullptr;
    }
    return (it->second)(a);
}

最后,我有一个“助手”类,可以将新类型的对象注册到工厂中。每次创建一个新的继承对象时,比如ObjectDerived,我都会添加(在实现ObjectDerived 的.cpp 文件中):

FactoryHelper&lt;ObjectDerived&gt; registerObjectDerived("ObjectDerived");

这将创建一个FactoryHelper&lt;ObjectDerived&gt; 类型的对象,其构造函数处理工厂中的注册。 FactoryHelperFactoryHelper.h 中定义(并实现):

template<class T>
class FactoryHelper
{
public:
    FactoryHelper(const std::string &);
    static Object *create(double);
};

template<class T>
FactoryHelper<T>::FactoryHelper(const std::string &ObjectId)
{
    Factory &theFactory = Factory::instance(); // the one and only!
    // if it doesn't exist at this point, it is created.
    theFactory.registerObject(ObjectId, FactoryHelper<T>::create);
}

template<class T>
Object *FactoryHelper<T>::create(double a)
{
    return new T(a);
}

所以我遇到的问题是我得到了一堆对Factory::instance() 的未定义引用,基本上每个层次结构中的每种类型的对象都有一个。

如果我将所有内容放在同一个 main.cpp 文件中,它可以工作,但这不是我想要的解决方案。

【问题讨论】:

  • 我无法重现您的错误。为了使您的代码完整,我在一个文件中添加了一个非常简单的 Object 类和 main() 函数。当然,我没有创建继承自 Object 的类的层次结构。您能否通过提供最小的基础对象和派生类以及主函数来创建一个 MCVE 来显示您的问题,并包含编译器错误?
  • @jwimberley 我实际上只是做了同样的事情,并且不得不编辑我的问题,因为显然我之前做错了什么:将所有内容放在同一个 cpp 文件中不会产生错误。但这并不是一个理想的解决方案,真的。
  • 在这种情况下,这似乎是编译/链接问题,而不是您的代码问题。

标签: c++ templates design-patterns singleton factory


【解决方案1】:

由于当您的所有代码都在一个文件中时没有编译错误,并且您没有使用任何可能导致多个文件出现问题的外部全局对象,我怀疑您的编译/链接脚本有问题。

作为记录,我可以确认您的代码没有内在问题。添加层次结构

class Object
{
public:
    Object(double _value) : value(_value) {}
    virtual double getVal() { return value; }
private:
    double value;
};

class SpecialObject : public Object
{
public:
    SpecialObject(double _value) : Object(_value) {}
    virtual double getVal() { double val = Object::getVal(); return val*val; }
};

简单的主程序

int main(int argc, char *argv[]) {
    FactoryHelper<Object> baseMaker("Object");
    FactoryHelper<SpecialObject> derivedMaker("SpecialObject");
    Factory& factory = Factory::instance();

    Object* a1 = factory.createObject("Object",4);
    std::cout << a1->getVal() << std::endl;
    Object* b1 = factory.createObject("SpecialObject",4);
    std::cout << b1->getVal() << std::endl;
    Object* c1 = factory.createObject("NonexistentObject",4);

    return 0;
}

有预期的输出:

4
16
NonexistentObject is an unknown object.

顺便说一句,您的FactoryHelper&lt;T&gt; 类并没有取得太多成就,本质上充当了使用默认分配器/构造器注册对象的快捷方式。在某些时候,创建新类实际上会停止节省大量代码。如果能用C++11,写起来也不会难很多

factory.registerObject("SpecialObject", [] (double a) -> Object* { return new SpecialObject(a); });

如果你愿意,你可以给Factory本身添加快捷方式:

// definition
template <class T>
void registerObject(const std::string &);

// implementation
template<class T>
void Factory::registerObject(const std::string &ObjectId)
{
    registerObject(ObjectId, [] (double a) -> Object* { return new T(a); });
};

这样,FactoryHelper类就可以去掉了,和之前等效的main例程是

using namespace std;
int main(int argc, char *argv[]) {
    Factory& factory = Factory::instance();
    factory.registerObject<Object>("Object");
    factory.registerObject<SpecialObject>("SpecialObject");

    Object* a1 = factory.createObject("Object",4);
    std::cout << a1->getVal() << std::endl;
    Object* b1 = factory.createObject("SpecialObject",4);
    std::cout << b1->getVal() << std::endl;
    Object* c1 = factory.createObject("NonexistentObject",4);

    return 0;

}

同样,如果你能够使用 C++11,你总是可以让 createObject 将原始的 Object* 指针包装在一个智能指针中(你可能很清楚,也许你已经有充分的理由不这样做了这样做)。

【讨论】:

  • 老实说,我不知道发生了什么。在 make clean 之后,它现在似乎也可以使用单独的文件。感谢您提供消除 FactoryHelper 的提示 :)
  • 我实际上遇到了新的麻烦。我可以在主例程中使用您的方法注册对象,但理想情况下,我想在我引入的每个新类(在它自己的 .cpp 中)上这样做。如果在其中一个文件上执行此操作,Factory&amp; factory = Factory::instance(); factory.registerObject&lt;Object&gt;("Object"); 行(在第二行中)会给我一个 factory 的“未知类型名称”错误。如果在 main() 上完成,则完全没有错误。
  • 嗯,你能发布完整的错误吗?您之前的策略是让这些类从 FactoryHelper 继承吗,例如WidgetObject : public FactoryHelper&lt;WidgetObject&gt;?
  • 您需要更具体地说明将此代码放在 Object.cpp 中的位置。它是类构造函数的一部分,还是它本身只是一行?我希望后一种情况会失败(使用我的 Factory 建议或 FactoryHelper),因为 Factory::registerObject&lt;T&gt;(std::string&amp;) 是一个函数调用,并且必须存在于 main 或 main 调用的某个函数中,原因有很多。把它放在构造函数中可能工作
  • 哦,我明白了。是的,它本身就是一条线,所以它失败是有道理的。我认为这是我使用 FactoryHelper 的主要原因,因为可以在构造函数中调用注册(这是我遵循的一个建议,没有过多考虑我在做什么)。我不想把它放在对象的构造函数中,因为我希望即使不存在对象也能进行注册。
猜你喜欢
  • 2011-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-04
相关资源
最近更新 更多