创建受模板约束的C ++工厂

时间:2019-06-12 19:04:51

标签: c++ c++17 factory template-meta-programming

我对元编程还很陌生,我正在尝试创建一个工厂的单例对象,该对象创建可克隆的对象。为此,我具有以下代码,这些代码也已在 live coliru

中共享

我要实现的目标(并且它未包含在coliru链接的代码中)是限制工厂仅允许其typename TICloneable接口。

ICloneable接口定义如下:

template <typename T>
class ICloneable {
public:
    virtual std::unique_ptr<T> clone() const = 0;
};

实例化工厂的代码如下所示。

我能看到的最接近的答案是factory of templated class。我当时认为类似类型特征的东西会有所帮助-例如std::is_same_v-但我对这些元编程技术没有经验。

int main()
{
    auto protoType = std::make_unique<Widget>(1,2);
    const auto gpFactory = Factory<Widget>::getInstance();
    gpFactory->registerType(std::move(protoType), 32u);
    auto cloned = gpFactory->getClone(32u);
    std::cout << *cloned;
    return 0;
}

2 个答案:

答案 0 :(得分:3)

第一个问题是gFactoryconst&registerType是非const方法。

auto& gFactory = Factory<Widget>::getInstance();

解决此问题。

gFactory.registerType(protoType, 32u);

registerType预期为unique_ptr<Widget>。您正在传递unique_ptr<Widget>,但是您正在尝试复制它。

您不能复制unique_ptr

gFactory.registerType(std::move(protoType), 32u);

接下来,这里缺少一个论点和类似的问题:

  const auto& [iter, inserted] = mFactoryRegInfo.try_emplace(rkey, std::move(protoType));

并且您在main中丢弃了nodiscard参数。

Live example

要求ICloneable<T>实际上被视为通用代码中的反模式。

template<class T, class C=std::unique_ptr<T>>
struct can_clone:std::false_type{};

template<class T>
struct can_clone<T, decltype( std::declval<T const&>().clone() )>:std::true_type {};

template <typename T,
  std::enable_if_t< can_clone<T>{}, bool > = true
>
class Factory final {
public:
    //! Thread safe singleton pattern
    static Factory& getInstance() {
        static std::unique_ptr<Factory> pInstance = std::make_unique<Factory>(token{0});
        return *pInstance;
    }

    //! Registers a new cloneable type in the factory.
    [[nodiscard]] bool registerType(std::unique_ptr<T> protoType, const uint32_t rkey) {
        // Critical Section
        std::lock_guard<std::mutex> lock(MutexGuard);
        const auto& [iter, inserted] = mFactoryRegInfo.try_emplace(rkey, std::move(protoType));
        return inserted;
    }

    //! Factory function - returns newly cloned unique_ptr<T>.
    [[nodiscard]] std::unique_ptr<T> getClone(const uint32_t rkey) const {
        // Critical Section
        std::lock_guard<std::mutex> lock(MutexGuard);
        const auto& iter = mFactoryRegInfo.find(rkey);
        if (iter != mFactoryRegInfo.end()) {
            return iter->second->clone();
        }
        return nullptr;
    }

    //! C.67: A polymorphic class should suppress copying.
    Factory(const Factory&) = delete;
    Factory(Factory&&) noexcept = delete;
    Factory& operator=(const Factory&) = delete;
    Factory& operator=(Factory&&) noexcept = delete;

    //! Defaulted destructor.
    ~Factory() = default;
private:
    //! Singleton private constructor.
    Factory() = default;
    struct token { explicit token(int){} };
public:
    explicit Factory(token):Factory() {}
private:

    // UUID (uint32_t) to T mapping
    std::map<uint32_t, std::unique_ptr<T>> mFactoryRegInfo{};

    mutable std::mutex MutexGuard;
};

这仅需要T有一个支持返回T::clone() const的{​​{1}}方法。

一种改进是要求它返回可转换为 unique_ptr<T>的类型。

还要注意,我清理了您的单例代码。注意,您不应将单例代码与功能代码混合使用;从单元测试到想要特定于文档的对象工厂,有很多原因在同一代码库中拥有多个unique_ptr<T>

如果需要,可以将Singleton作为模板元编程的单独一部分来实现。

当您意识到混合动态库加载时单身人士的生命变得异常复杂时,这将挽救您的生命。

Live example

答案 1 :(得分:3)

您可以在Factory内使用这样的静态断言来确保T实现ICloneable<T>

static_assert(std::is_convertible<T*, ICloneable<T>*>::value, "T must implement ICloneable<T>");

Live example

请注意,尽管存在std::is_base_of特征,但std::is_convertible确实是在这种情况下可以使用的正确类型特征。引用cppreference

  即使std::is_base_of<A, B>::valueA的私有,受保护或模棱两可的基类,

B也是正确的。在许多情况下,std::is_convertible<B*, A*>是更合适的测试。