在C++中实现抽象工厂 PIMPL 习语的运行时错误

Runtime error implementing Abstract Factory PIMPL Idiom in C++

本文关键字:PIMPL 习语 运行时错误 工厂 抽象 C++ 实现      更新时间:2023-10-16

当尝试在 PIMPL 习惯用法下实现抽象工厂时,我在尝试从工厂范围之外获取对象时出现运行时错误。(请参阅在 Main 中注释为"运行时错误"的部分。当从公共类调用 acquireInterface(( 方法时会发生这种情况,该方法从实现调用 acquireInterface((。但是,当从实现类中的testFactory((函数获取Interface((时,不会发生这种情况(请参阅"testFactory(("函数(。有什么建议吗?在启用RTTI的MinGW 4.8和VC++11中尝试。

提前谢谢。

--- 文件: IType.hpp ---

#ifndef ITYPE_HPP_
#define ITYPE_HPP_

class ITYPE {
public:
    ITYPE() {
        std::cout << "ITYPE()" << std::endl;
    };
    virtual ~ITYPE(){
        std::cout << "~ITYPE()" << std::endl;
    };
    virtual void hello() = 0;
};

#endif /* ITYPE_HPP_ */

--- 文件: 工厂.hpp ---

#ifndef FACTORY_HPP
#define FACTORY_HPP
#include <memory>
#include <iostream>
#include "IType.hpp"
class Factory {
public:
    Factory();
    ~Factory();
    void testFactory();
    ITYPE* acquireInterface(const char* type);
private:
    class Impl;
    std::unique_ptr<Impl> m_pimpl;
};
#endif //FACTORY_HPP

--- 文件:工厂.cpp ---

// Implementations
// ----------------------------------------------------------------------
class ConcreteA : public ITYPE {
public:
    ConcreteA(){ std::cout << "ConcreteA()" << std::endl; }
    ~ConcreteA(){ std::cout << "~ConcreteA()" << std::endl; }
    void hello() { std::cout << "A says Hello" << std::endl; }
};
class ConcreteB : public ITYPE {
public:
    ConcreteB(){ std::cout << "ConcreteB()" << std::endl; }
    ~ConcreteB(){ std::cout << "~ConcreteB()" << std::endl; }
    void hello() { std::cout << "B says Hello" << std::endl; }
};

// ----------------------------------------------------------------------

template<typename Type> ITYPE* createType()
{
    return new Type();
}
/**
 * @brief Abstract Factory for ITYPES.
 */
class Factory::Impl {
    public:
        /**
         * @brief Constructor
         * @details Implementations to be added here using function addType()
         */
        Impl() {
            addType<ConcreteA>("A");
            addType<ConcreteB>("B");
        };
        ITYPE* acquireInterface(const char* type)
        {           
            std::cout << "Acquiring interface for " << type << "..." << std::endl;
            Impl::map_type::iterator iter = m_map.find(type);
            return iter->second();      
        }
    // THIS WORKS
    void testFactory()
    {
        ITYPE* iA = acquireInterface("A");
        iA->hello();
        delete iA;
        ITYPE* iB = acquireInterface("B");
        iB->hello();
        delete iB;
    }
    private:
        /** @brief Adds a type to the Abstract Factory
         *  @param componentName short string (no spaces) to identify implementation */
        template<typename Type>
        void addType(const char* componentName) {
            ComponentFactoryFuncPtr function = createType<Type>;
            m_map.insert(std::make_pair(componentName, function));
        };
public:
        /**
         * @brief Abstract factory constructor function pointer
         */
        typedef  ITYPE* (*ComponentFactoryFuncPtr)();
        /**
         * @brief Type for map holding type identifier / constructor function
         */
        typedef std::map<const char*, ComponentFactoryFuncPtr> map_type;
private:
     map_type m_map;    /**< map holding type identifier / constructor function */
};
Factory::Factory() : m_pimpl(new Impl()) { }
Factory::~Factory() { }
void Factory::testFactory()
{
    m_pimpl->testFactory();
}
ITYPE* Factory::acquireInterface(const char* type)
{
    return m_pimpl->acquireInterface(type);
}

---主要---

#include <iostream>
#include <memory>
using namespace std;
#include "Factory.hpp"
int main() 
{
    {
        Factory f;
        // OK
        std::cout << "This works:"  << std::endl;
        f.testFactory();
        // Runtime error (acquireInterface("A") returns NULL ptr)
        ITYPE* iA = f.acquireInterface("A");
        iA->hello();
        delete iA;
        ITYPE* iB = f.acquireInterface("B");
        iB->hello();
        delete iB;
    }
    return getchar();
}

代码中的一个坏事是:

typedef std::map<const char*, ComponentFactoryFuncPtr> map_type;

主要问题是,即使文本相同,也无法保证const char*文本具有相同的地址。

您的代码会尝试此操作:

ITYPE* iA = f.acquireInterface("A");

无法保证字符串文本"A"的指针值与您设置地图时使用的"A"相同。 因此,行为是不确定将会发生什么的。

如果map键的目标是有一个字符串,那么使用一个字符串。 您现在可以完全控制键表示的内容,而不是您不知道编译器如何处理字符串文本的const char *。 你真正知道的是"A"是一个字符串文字,但这就是你真正能知道的。

修复应该是这样的:

typedef std::map<std::string, ComponentFactoryFuncPtr> map_type;