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

Runtime error implementing Abstract Factory PIMPL Idiom in C++

在 PIMPL 习惯用法下尝试实现抽象工厂时,在尝试从工厂范围外获取对象时出现运行时错误。 (请参阅 Main 中用 "Runtime Error" 注释的部分。当从 public class 调用 acquireInterface() 方法时会发生这种情况,后者从实现中调用 acquireInterface() )。 但是,当实现 class 中的 testFactory() 函数中的 acquireInterface() 时,这不会发生(参见 "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_ */

--- 文件:Factory.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

--- 文件:FactoryImpl.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;